Adobe CQ5 custom servlet path - servlets

I am trying to add some functionality to json processing for some nodes. So I wrote custom servlet extended from SlingSafeMethodsServlet which I need to be executed when user makes GET for the following url : /data/events/any_sequence/any_sequence.json or /data/events/any_sequence/any_sequence.infinity.json or for example /data/events/any_sequence/any_sequence.2.json where any_sequence of course means any valid sequence of symbols.
The problem is that I cannot find in the sling docs how to map this template like urls.
I've been trying to set properties like this:
#Component
#Service
#Properties({
#Property(name = "sling.servlet.resourceTypes", value = "data/events/-/-"),
#Property(name = "sling.servlet.extensions", value = "json"),
#Property(name = "sling.servlet.methods", value = "GET"),
#Property(name = "service.description", value = "JSON advanced renderer")
})
But it didn't help. I checked felix console and found out that my service had started and running, so the problem is how to set url mappings. So my question is how to set url mapping in my case to invoke doGet of my custom servlet ?
Thanks.

As far as I understand CQ5 does not provide ability to map custom servlets on wildcard urls. The only way to accomplish goal similiar to one I needed is to use some unique for this servlet selector like this:
#Component
#Service
#Properties({
#Property(name = "sling.servlet.resourceTypes", value = "sling/servlet/default"),
#Property(name = "sling.servlet.extensions", value = "json"),
#Property(name = "sling.servlet.selectors", value = "advanced"),
#Property(name = "sling.servlet.methods", value = "GET"),
#Property(name = "service.description", value = "JSON advanced renderer")
})
This code means that if I'll try to make GET on some node with *.advanced.json selector and extension then request will be forwarded to my custom servlet.
See http://apache-sling.73963.n3.nabble.com/Register-servlet-for-subtree-td84106.html

I've solved this, exactly as the original poster was hoping. All other answers are effectively "it can't be done" or "here's a way to do it if you're willing to dirty up your clean RESTful API with selectors"
If you want to keep the clean API you envisioned, here's how. This works also for Apis without extensions, like /myservice/mythings/123123 , where 123123 is some dynamic ID
Create Two Files:
ResourceProvider
Servlet
The ResourceProvider
The purpose of this is only to listen to all requests at /data/events and then produce a "Resource" at that virtual path, which doesn't actually exist in the JCR.
#Component
#Service(value=ResourceProvider.class)
#Properties({
#Property(name = ResourceProvider.ROOTS, value = "data/events"),
#Property(name = ResourceProvider.OWNS_ROOTS, value = "true")
})
public class ImageResourceProvider implements ResourceProvider {
#Override
public Resource getResource(ResourceResolver resourceResolver, String path) {
AbstractResource abstractResource;
abstractResource = new AbstractResource() {
#Override
public String getResourceType() {
return TypeServlet.RESOURCE_TYPE;
}
#Override
public String getResourceSuperType() {
return null;
}
#Override
public String getPath() {
return path;
}
#Override
public ResourceResolver getResourceResolver() {
return resourceResolver;
}
#Override
public ResourceMetadata getResourceMetadata() {
return new ResourceMetadata();
}
};
return abstractResource;
}
#Override
public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest httpServletRequest, String path) {
return getResource(resourceResolver , path);
}
#Override
public Iterator<Resource> listChildren(Resource resource) {
return null;
}
}
The Servlet
Now you just write a servlet which handles any of the resources coming from that path - but this is accomplished by handling any resources with the resource type which is produced by the ResourceProvider listening at that path.
#SlingServlet(
resourceTypes = TypeServlet.RESOURCE_TYPE,
methods = {"GET" , "POST"})
public class TypeServlet extends SlingAllMethodsServlet {
static final String RESOURCE_TYPE = "mycompany/components/service/myservice";
#Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
final String [] pathParts = request.getResource().getPath().split("/");
final String id = pathParts[pathParts.length-1];
response.setContentType("text/html");
PrintWriter out = response.getWriter();
try {
out.print("<html><body>Hello, received this id: " + id + "</body></html>");
} finally {
out.close();
}
}
}

This is the syntax I have used to accomplish similar tasks:
#Component(immediate = true, description = "JSON advanced renderer")
#Service(value = javax.servlet.Servlet.class)
#Properties(value = {
#Property(name = "sling.servlet.extensions", value = { "json" }),
#Property(name = "sling.servlet.methods", value = { "GET" }),
#Property(name = "sling.servlet.paths", value = {
"/data/events/any_sequence/any_sequence",
"/data/events/any_sequence/any_sequence.infinity",
"/data/events/any_sequence/any_sequence.2"
})
})

I had a similar problem and I needed wildcards I used
#Service
#Component
#Properties({
#Property(name = "sling.servlet.paths", value = "/bin/resolver/gb"),
#Property(name = "sling.servlet.extensions", value = "*")
})
public class Test extends SlingAllMethodsServlet {
#Override
public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.print("The path you used is:" + request.getPathInfo());
}
}
Call is [server]:[port]/bin/resolver/gb.[wildcard]
So what can be done is : [server]:[port]/bin/resolver/gb.en/something
Everything after "." is considered an extension so need to be handled in the servlet, but helped me achieve my requirement

It seems that the path of your servlet is same.Just selectors are varying. When used with path, other things are ignored in SlingServlet. So using something like this should serve the purpose:
#SlingServlet(paths = " /data/events/any_sequence/any_sequence", extensions = "json")
You would need to add /data in execution paths from Felix console(/system/console/configMgr) as is it not there by default in Apache Sling Servlet Resolver property

This can be accomplished using the desired external URI pattern by constructing a Sling mapping or Apache rewrite to effectively move the JSON extension to just after "data" in the URI, so the single servlet at "/data" ends up receiving the arbitrary path via the suffix of the request. If you are also using data from selectors, you'll need to move them along with the extension.

Related

Spring Boot MVC Test 404 with Valid Request

I am using Spring Boot 2.0.6 and have set up a test for a controller: the method is as follows:
#Secured("ROLE_ADMIN")
#GetMapping(value = {"/maintainers/aircrafts/workorders/workitems/{wid}/parts"}, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
Response<Page<WorkItem>> getPagedParts(
#PathVariable("wid") Optional<Long> workItemId,
#PageableDefault(page = DEFAULT_PAGE_NUMBER, size = DEFAULT_PAGE_SIZE)
#SortDefault.SortDefaults({
#SortDefault(sort = "partName", direction = Sort.Direction.ASC),
#SortDefault(sort = "partSpecification", direction = Sort.Direction.ASC)
}) Pageable pageable) {
LOG.info("looking for work: {}", workItemId);
return Response.of(workItemService.findAllPartsForWorkItem(workItemId.get(), pageable));
}
As you can see, it is supposed to do paging and sorting, but it doesn't even get past the path:
The test that tests it is as follows:
#ActiveProfiles("embedded")
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#EnableConfigurationProperties
#EnableJpaRepositories({ "au.com.avmaint.api" })
#AutoConfigureMockMvc
public class WorkItemControllerPartsFunctionalTest {
private static final Logger LOG = LoggerFactory.getLogger(WorkItemControllerFunctionalTest.class);
private String adminJwtToken;
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#Autowired
private UserService userService;
#Autowired
private RoleService roleService;
#Autowired
private CasaService casaService;
#Autowired
private MaintainerService maintainerService;
#Autowired
private MaintenanceContractService maintenanceContractService;
#Autowired
private WorkSetService workSetService;
#Autowired
private WorkSetTemplateService workSetTemplateService;
#Autowired
private AircraftService aircraftService;
Maintainer franks;
MaintenanceContract contract;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
franks = MaintainerFixtures.createFranksMaintainer(maintainerService, maintenanceContractService, casaService);
adminJwtToken = UserAndRoleFixtures.adminToken(userService, roleService, franks);
contract = WorkItemFixtures.makeDetailedJobOnContract(franks, maintainerService, maintenanceContractService, workSetTemplateService, casaService, aircraftService);
}
#Test
public void findingWorkItemsWithoutParts() throws Exception {
Set<WorkSet> sets = contract.getWorkOrders().stream().findFirst().get().getWorkSets();
WorkSet hundredHourly = sets.stream().filter(s -> s.getName().equals("100 Hourly for PA-31")).findFirst().orElse(null);
WorkItem opening = hundredHourly.getWorkItems().stream().filter(wi -> wi.getTitle().equals("Opening the aircraft")).findFirst().orElse(null);
LOG.info("opening item: {}", opening);
LOG.info("HUNDRED: {}", hundredHourly);
mvc.perform(get("/maintainers/aircrafts/workorders/workitems/" + opening.getId() + "/parts")
.header(AUTHORIZATION_HEADER, "Bearer " + adminJwtToken))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.payload").isNotEmpty())
.andExpect(jsonPath("$.payload.content").isNotEmpty())
.andExpect(jsonPath("$.payload.pageable").isNotEmpty())
.andExpect(jsonPath("$.payload.last").value(false))
.andExpect(jsonPath("$.payload.totalPages").value(3)) // page count
.andExpect(jsonPath("$.payload.totalElements").value(9)) // total count
.andExpect(jsonPath("$.payload.size").value(4)) // elements per page
.andExpect(jsonPath("$.payload.numberOfElements").value(4)) // elements in page
.andExpect(jsonPath("$.payload.number").value(0)) // current page number
.andExpect(jsonPath("$.payload.content").isArray())
// oops, lets not check dates, they're created on the instant
.andExpect(jsonPath("$.payload.content[0].pos").value("1"))
.andExpect(jsonPath("$.payload.content[0].title").value("Opening the aircraft"))
.andExpect(jsonPath("$.payload.content[0].category").value("AIRFRAME"))
;
}
#After
public void tearDown() {
MaintainerFixtures.removeFranks(franks, maintainerService, aircraftService);
WorkItemFixtures.killJobs(workSetService, workSetTemplateService);
UserAndRoleFixtures.killAllUsers(userService, roleService);
}
}
As the project makes extensive use of JPA, there are annotations and a lot of data setup, but all of this has worked fine with other tests and there don't appear to be any problems with the data. In fact a peek at the JSON output for the work order that this method should be querying...
work order JSON
Basically has all the data correctly set up. The spring boot startup includes this line:
2018-11-12 06:32:17.362 INFO 83372 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/maintainers/aircrafts/workorders/workitems/{wid}/parts],methods=[GET],produces=[application/json]}" onto au.com.avmaint.api.common.Response<org.springframework.data.domain.Page<au.com.avmaint.api.aircraft.model.WorkItem>> au.com.avmaint.api.aircraft.WorkItemController.getPagedParts(java.util.Optional<java.lang.Long>,org.springframework.data.domain.Pageable)
So the path appears to be OK
and now to the .andDo(print()) output:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /maintainers/aircrafts/workorders/workitems/5/parts
Parameters = {}
Headers = {Authorization=[Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJmcmFua0BmcmFua3MuY29tIiwic2NvcGVzIjpbIlJPTEVfQURNSU4iLCJST0xFX0JBU0lDIl0sImV4cCI6MTU0MjgyODczOH0.QOTiyWG_pVL9qb8MDG-2c_nkTnsIzceUH-5vvtmpZhBcdro9HqVADojK0-c6B1sAOOYOcprpwg4-wrBF0PGweg]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
and the 404. So I guess I'm breaking something somewhere, I just can't see what it is, can anyone help with this?
Sorry everyone, the effect of tearing my hair out for ages, finally posting the question and then finding the problem moments later.
The issue was that I forgot to put /api as the prefix on the path in the test. This prefix is put on the top of every controller with:
#RestController
#RequestMapping("/api")
public class WorkItemController {
so, yeah: it works now

How to get basePackages of #ComponentScan programatically at runtime?

The scanBasePackages of #SpringBootApplication configured as follow:
package com.xxx.boot.sample;
#SpringBootApplication(scanBasePackages = { "com.xxx.boot.sample", "com.xxx.boot.service" })
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
The requirement is we want to integration Apache Dubbo component annotation scan with Spring Boot by programming at runtime for zero properties configuration, not by annotation.
#Configuration
#ConditionalOnClass({ EnableDubboConfig.class, AbstractConfig.class })
#ConditionalOnProperty(prefix = "dubbo", name = "enabled", havingValue = "true", matchIfMissing = true)
public class DubboAutoConfiguration {
/// Dubbo配置
#Configuration
#EnableDubboConfig
#ConditionalOnProperty(prefix = "dubbo.config", name = "enabled", havingValue = "true", matchIfMissing = true)
#EnableConfigurationProperties(DubboProperties.class)
public static class DubboConfigConfiguration {
}
/// Dubbo注解扫描
#Configuration
#ConditionalOnClass({ Service.class, Reference.class })
#ConditionalOnProperty(prefix = "dubbo.annotation", name = "enabled", havingValue = "true", matchIfMissing = true)
public static class DubboAnnotationConfiguration {
#Bean(name = "serviceAnnotationBeanPostProcessor")
#ConditionalOnMissingBean
public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor(BeanFactory beanFactory) {
// 获取 Spring Boot 主入口类所在的包路径
List<String> packagesToScan = AutoConfigurationPackages.get(beanFactory);
if (packagesToScan == null) {
packagesToScan = Collections.emptyList();
}
return new ServiceAnnotationBeanPostProcessor(packagesToScan);
}
#Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME)
#ConditionalOnMissingBean
public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() {
return new ReferenceAnnotationBeanPostProcessor();
}
}
}
But AutoConfigurationPackages#get(BeanFactory) only return "com.xxx.boot.sample", not include "com.xxx.boot.service". I hope return all scanBasePackages value.
By debug, I found the #SpringBootApplication instance is a proxy class instance. I try to get Annotation use Class#getAnnotations, then get scanBasePackages field by reflection. But not success.
Question:
How to get scanBasePackages of #SpringBootApplication or basePackages of #ComponentScan programatically?
It isn't really advisable to try to reuse the scanBasePackages attributes for your own purposes. If you look at the source of #SpringBootApplication you'll see the following:
#AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
#AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
This is saying that these attributes are an alias for #ComponentScan. Since this annotation can be used on any #Configuration class it's actually legal to have many of them.
The #ComponentScan annotation triggers scanning by the ConfigurationClassParser. Look at the doProcessConfigurationClass method for all the gory details.
If you really want to find the annotation attributes yourself you can do the following:
applicationContext.getBeansWithAnnotation(ComponentScan.class).forEach((name, instance) -> {
Set<ComponentScan> scans = AnnotatedElementUtils.getMergedRepeatableAnnotations(instance.getClass(), ComponentScan.class);
for (ComponentScan scan : scans) {
System.out.println(Arrays.toString(scan.basePackageClasses()));
System.out.println(Arrays.toString(scan.basePackages()));
}
});
This will just get you those two values. You're still not considering any #Condition annotations or any include/exclude filters. You also won't deal with #ComponentScan() which means scan from the current package down.
What Spring Boot tends to do in these circumstances is define a new annotation for a specific purpose. For example, you can use #EntityScan to define where JPA entities are found. We then use AutoConfigurationPackages as the default value if you don't specify any override.

Spring Data, MVC DomainClassConverter how to get to id from request and keep using domain type as param

As part of Spring Data, there is DomainClassConverter which helps with repository lookups so that we don't have to do lookup manually.
http://docs.spring.io/spring-data/commons/docs/current/reference/html/#core.web.basic.domain-class-converter
#Controller
#RequestMapping("/orders/{id}")
public class PaymentController {
#RequestMapping(path = "/payment", method = PUT)
ResponseEntity<?> submitPayment(#PathVariable("id") Order order) {
if (order == null) {
throw new OrderNotFoundException(???orderId???);
}
...
}
}
How can I get to {id} from request without changing Order parameter to Long?
DomainClassConverter converts {id} to null as expected
My intention is to use order id in OrderNotFoundException
I can think of couple of ways.
1. Change the method to
#RequestMapping(path = "/payment", method = PUT)
ResponseEntity<?> submitPayment(#PathVariable("id") Order order, HttpServletRequest request) {
String path = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString()
// path will have "/payment/id". use substring or something similar to get just id
}
Write a #Around Aspect for #RequestMapping and get the argument
#Aspect
#Configuration
public class ControllerAspect {
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {}
#Around("requestMapping()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String path = (String) joinPoint.getArgs()[0];
Object result= joinPoint.proceed();
}
}

Override host of webapi odata links

I'm using WebAPI 2.2 and Microsoft.AspNet.OData 5.7.0 to create an OData service that supports paging.
When hosted in the production environment, the WebAPI lives on a server that is not exposed externally, hence the various links returned in the OData response such as the #odata.context and #odata.nextLink point to the internal IP address e.g. http://192.168.X.X/<AccountName>/api/... etc.
I've been able to modify the Request.ODataProperties().NextLink by implementing some logic in each and every ODataController method to replace the internal URL with an external URL like https://account-name.domain.com/api/..., but this is very inconvenient and it only fixes the NextLinks.
Is there some way to set an external host name at configuration time of the OData service? I've seen a property Request.ODataProperties().Path and wonder if it's possible to set a base path at the config.MapODataServiceRoute("odata", "odata", GetModel()); call, or in the GetModel() implementation using for instance the ODataConventionModelBuilder?
UPDATE: The best solution I've come up with so far, is to create a BaseODataController that overrides the Initialize method and checks whether the Request.RequestUri.Host.StartsWith("beginning-of-known-internal-IP-address") and then do a RequestUri rewrite like so:
var externalAddress = ConfigClient.Get().ExternalAddress; // e.g. https://account-name.domain.com
var account = ConfigClient.Get().Id; // e.g. AccountName
var uriToReplace = new Uri(new Uri("http://" + Request.RequestUri.Host), account);
string originalUri = Request.RequestUri.AbsoluteUri;
Request.RequestUri = new Uri(Request.RequestUri.AbsoluteUri.Replace(uriToReplace.AbsoluteUri, externalAddress));
string newUri = Request.RequestUri.AbsoluteUri;
this.GetLogger().Info($"Request URI was rewritten from {originalUri} to {newUri}");
This perfectly fixes the #odata.nextLink URLs for all controllers, but for some reason the #odata.context URLs still get the AccountName part (e.g. https://account-name.domain.com/AccountName/api/odata/$metadata#ControllerName) so they still don't work.
Rewriting the RequestUri is sufficient to affect #odata.nextLink values because the code that computes the next link depends on the RequestUri directly. The other #odata.xxx links are computed via a UrlHelper, which is somehow referencing the path from the original request URI. (Hence the AccountName you see in your #odata.context link. I've seen this behavior in my code, but I haven't been able to track down the source of the cached URI path.)
Rather than rewrite the RequestUri, we can solve the problem by creating a CustomUrlHelper class to rewrite OData links on the fly. The new GetNextPageLink method will handle #odata.nextLink rewrites, and the Link method override will handle all other rewrites.
public class CustomUrlHelper : System.Web.Http.Routing.UrlHelper
{
public CustomUrlHelper(HttpRequestMessage request) : base(request)
{ }
// Change these strings to suit your specific needs.
private static readonly string ODataRouteName = "ODataRoute"; // Must be the same as used in api config
private static readonly string TargetPrefix = "http://localhost:8080/somePathPrefix";
private static readonly int TargetPrefixLength = TargetPrefix.Length;
private static readonly string ReplacementPrefix = "http://www.contoso.com"; // Do not end with slash
// Helper method.
protected string ReplaceTargetPrefix(string link)
{
if (link.StartsWith(TargetPrefix))
{
if (link.Length == TargetPrefixLength)
{
link = ReplacementPrefix;
}
else if (link[TargetPrefixLength] == '/')
{
link = ReplacementPrefix + link.Substring(TargetPrefixLength);
}
}
return link;
}
public override string Link(string routeName, IDictionary<string, object> routeValues)
{
var link = base.Link(routeName, routeValues);
if (routeName == ODataRouteName)
{
link = this.ReplaceTargetPrefix(link);
}
return link;
}
public Uri GetNextPageLink(int pageSize)
{
return new Uri(this.ReplaceTargetPrefix(this.Request.GetNextPageLink(pageSize).ToString()));
}
}
Wire-up the CustomUrlHelper in the Initialize method of a base controller class.
public abstract class BaseODataController : ODataController
{
protected abstract int DefaultPageSize { get; }
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
var helper = new CustomUrlHelper(controllerContext.Request);
controllerContext.RequestContext.Url = helper;
controllerContext.Request.ODataProperties().NextLink = helper.GetNextPageLink(this.DefaultPageSize);
}
Note in the above that the page size will be the same for all actions in a given controller class. You can work around this limitation by moving the assignment of ODataProperties().NextLink to the body of a specific action method as follows:
var helper = this.RequestContext.Url as CustomUrlHelper;
this.Request.ODataProperties().NextLink = helper.GetNextPageLink(otherPageSize);
The answer by lencharest is promising, but I found an improvement on his method. Rather than using the UrlHelper, I created a class derived from System.Net.Http.DelegatingHandler. This class is inserted (first) into the message handling pipeline and thus has a crack at altering the incoming HttpRequestMessage. It's an improvement over the above solution because in addition to altering the controller-specific URLs (as the UrlHelper does, e,g, https://data.contoso.com/odata/MyController), it also alters the url that appears as the xml:base in the OData service document (e.g., https://data.contoso.com/odata).
My particular application was to host an OData service behind a proxy server, and I wanted all the URLs presented by the server to be the externally-visible URLs, not the internally-visible ones. And, I didn't want to have to rely on annotations for this; I wanted it to be fully automatic.
The message handler looks like this:
public class BehindProxyMessageHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var builder = new UriBuilder(request.RequestUri);
var visibleHost = builder.Host;
var visibleScheme = builder.Scheme;
var visiblePort = builder.Port;
if (request.Headers.Contains("X-Forwarded-Host"))
{
string[] forwardedHosts = request.Headers.GetValues("X-Forwarded-Host").First().Split(new char[] { ',' });
visibleHost = forwardedHosts[0].Trim();
}
if (request.Headers.Contains("X-Forwarded-Proto"))
{
visibleScheme = request.Headers.GetValues("X-Forwarded-Proto").First();
}
if (request.Headers.Contains("X-Forwarded-Port"))
{
try
{
visiblePort = int.Parse(request.Headers.GetValues("X-Forwarded-Port").First());
}
catch (Exception)
{ }
}
builder.Host = visibleHost;
builder.Scheme = visibleScheme;
builder.Port = visiblePort;
request.RequestUri = builder.Uri;
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
You wire the handler up in WebApiConfig.cs:
config.Routes.MapODataServiceRoute(
routeName: "odata",
routePrefix: "odata",
model: builder.GetEdmModel(),
pathHandler: new DefaultODataPathHandler(),
routingConventions: ODataRoutingConventions.CreateDefault()
);
config.MessageHandlers.Insert(0, new BehindProxyMessageHandler());
There is another solution, but it overrides url for the entire context.
What I'd like to suggest is:
Create owin middleware and override Host and Scheme properties inside
Register the middleware as the first one
Here is an example of middleware
public class RewriteUrlMiddleware : OwinMiddleware
{
public RewriteUrlMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
context.Request.Host = new HostString(Settings.Default.ProxyHost);
context.Request.Scheme = Settings.Default.ProxyScheme;
await Next.Invoke(context);
}
}
ProxyHost is the host you want to have. Example: test.com
ProxyScheme is the scheme you want: Example: https
Example of middleware registration
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(RewriteUrlMiddleware));
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
}
}
A couple of years later, using ASP.NET Core, I figured that the easiest way to apply it in my service was to just create a filter that masquerades the host name. (AppConfig is a custom configuration class that contains the host name, among other things.)
public class MasqueradeHostFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var appConfig = context.HttpContext.RequestServices.GetService<AppConfig>();
if (!string.IsNullOrEmpty(appConfig?.MasqueradeHost))
context.HttpContext.Request.Host = new HostString(appConfig.MasqueradeHost);
}
}
Apply the filter to the controller base class.
[MasqueradeHostFilter]
public class AppODataController : ODataController
{
}
The result is a nicely formatted output:
{ "#odata.context":"https://app.example.com/odata/$metadata" }
Just my two cents.
Using system.web.odata 6.0.0.0.
Setting the NextLink property too soon is problematic. Every reply will then have a nextLink in it. The last page should of course be free of such decorations.
http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793048 says:
URLs present in a payload (whether request or response) MAY be
represented as relative URLs.
One way that I hope will work is to override EnableQueryAttribute:
public class myEnableQueryAttribute : EnableQueryAttribute
{
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
var result = base.ApplyQuery(queryable, queryOptions);
var nextlink = queryOptions.Request.ODataProperties().NextLink;
if (nextlink != null)
queryOptions.Request.ODataProperties().NextLink = queryOptions.Request.RequestUri.MakeRelativeUri(nextlink);
return result;
}
}
ApplyQuery() is where the "overflow" is detected. It basically asks for pagesize+1 rows and will set NextLink if the result set contains more than pagesize rows.
At this point it is relatively easy to rewrite NextLink to a relative URL.
The downside is that every odata method must now be adorned with the new myEnableQuery attribute:
[myEnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<TElement> options)
{
...
}
and other URLs embedded elsewhere remains problematic. odata.context remains a problem. I want to avoid playing with the request URL, because I fail to see how that is maintainable over time.
Your question boils down to controlling the service root URI from within the service itself. My first thought was to look for a hook on the media type formatters used to serialize responses. ODataMediaTypeFormatter.MessageWriterSettings.PayloadBaseUri and ODataMediaTypeFormatter.MessageWriterSettings.ODataUri.ServiceRoot are both settable properties that suggest a solution. Unfortunately, ODataMediaTypeFormatter resets these properties on every call to WriteToStreamAsync.
The work-around is not obvious, but if you dig through the source code you'll eventually reach a call to IODataPathHandler.Link. A path handler is an OData extension point, so you can create a custom path handler that always returns an absolute URI which begins with the service root you desire.
public class CustomPathHandler : DefaultODataPathHandler
{
private const string ServiceRoot = "http://example.com/";
public override string Link(ODataPath path)
{
return ServiceRoot + base.Link(path);
}
}
And then register that path handler during service configuration.
// config is an instance of HttpConfiguration
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel(),
pathHandler: new CustomPathHandler(),
routingConventions: ODataRoutingConventions.CreateDefault()
);

Urls not detected that ends with .ico in Spring Boot

I am using this annotation within a Controller's method in one Spring Boot app.
#RequestMapping(value="/{x}/{y}/{filename:.*}", method = RequestMethod.GET)
All is working good and the last parameter can be any filename.
The problem is with urls where that filename ends with ".ico"...Spring is not sending the request to this method...my guess it is that it thinks a favicon itself.
How can I avoid this kind of conflict?
Thanks.
Have a look at Spring MVC #PathVariable with dot (.) is getting truncated, especially one of the latest answers regarding Spring 4.x
I found the solution. I just need to disable this setting inside the application.properties file
spring.mvc.favicon.enabled=false
This way the FaviconConfiguration bean from WebMvcAutoConfiguration does not satisfies the constraint, thus is not created:
#Configuration
#ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration implements ResourceLoaderAware {
private ResourceLoader resourceLoader;
#Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Integer.MIN_VALUE + 1);
/**THIS WAS THE CONFLICTIVE MAPPING IN MY CASE**/
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler()));
return mapping;
}
#Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
#Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler.setLocations(getLocations());
return requestHandler;
}
private List<Resource> getLocations() {
List<Resource> locations = new ArrayList<Resource>(CLASSPATH_RESOURCE_LOCATIONS.length + 1);
for (String location : CLASSPATH_RESOURCE_LOCATIONS) {
locations.add(this.resourceLoader.getResource(location));
}
locations.add(new ClassPathResource("/"));
return Collections.unmodifiableList(locations);
}
}
Source: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java

Resources