So I have a Jetty container with a URL like this: http://localhost:7071/my-app-name/ui/. The problem I'm running into is that Jetty seems to require that last trailing slash.
So this works:
http://localhost:7071/my-app-name/ui/
But this doesn't:
http://localhost:7071/my-app-name/ui
It's really weird, I would want the slash-less path to go to the same place as the path with a slash. That path is the homepage of the entire application, which is why I want it to work.
Here is some of my Jetty configuration.
PS. I'm working in Kotlin. The principles of how it works should still be the same though.
server = Server(props.getProperty(AppProps.PORT).toInt())
val handler = ServletContextHandler(server, root)
val appServletHolder = ServletHolder("AppServlet", AppServlet::class.java)
handler.addServlet(appServletHolder, "/ui/*")
handler.addEventListener(AppCore(props))
server.start()
I know I'm specifying "/ui/*" in my servlet holder configuration. But I can't seem to figure out any way to change that, I've tried a few combinations and none of them work better.
You've told the servlet spec that you want to listen on /ui/* so it mandates that your URLs must have the /ui/ portion.
But all is not lost, just tell the servlet spec the other url-patterns you are also interested in. (Servlets can be mapped to as many url-patterns as you want)
aka:
server = Server(props.getProperty(AppProps.PORT).toInt());
val handler = ServletContextHandler(server, root);
val appServletHolder = ServletHolder("AppServlet", AppServlet::class.java);
handler.addServlet(appServletHolder, "/ui"); // <-- like that
handler.addServlet(appServletHolder, "/ui/*");
handler.addEventListener(AppCore(props));
server.start();
Instead of adding a bunch of duplicate servlet mappings, I added one at the root to redirect any requests ending with "/" or "/index.html":
...
servletHandler.addServletWithMapping(RedirectServlet.class, "/*");
servletHandler.addServletWithMapping(AnotherServlet.class, "/ui");
...
public class RedirectServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
String pathInfo = req.getPathInfo();
if(pathInfo.endsWith("/") || pathInfo.endsWith("/index.html")) {
String newPath = pathInfo.substring(0, pathInfo.lastIndexOf('/'));
resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
resp.setHeader("Location", newPath);
}
}
#Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) {
doGet(req, resp);
}
}
Related
I need the following:
Any request to
https://localhost:8443
https://localhost:8443/
https://localhost:8443/test
https://localhost:8443/api
and so on, should be forwarded to
https://localhost:8443/a/web/index.html
Now, this is how I managed to do that:
#Controller
public class ForwardController {
#RequestMapping(value = "/*", method = RequestMethod.GET)
public String redirectRoot(HttpServletRequest request) {
return "forward:/a/web/index.html";
}
}
The problem is:
This also matches https://localhost:8443/api/ (note the / at the end).
This is a problem because that's where I want the Spring Data REST base path to be:
spring.data.rest.base-path=/api/
/api != /api/ when it comes to REST endpoints.
What should work but somehow doesn't
I have tried several different regular expressions but I am still not able to accomplish what I want. For example (demo):
#RequestMapping(value = "/[^/]+", method = RequestMethod.GET)
Will now work for Spring Data - I'm getting all the resource information I expect, but accessing https://localhost:8443/ is now broken and the web-client cannot be reached anymore.
The same goes for
#RequestMapping(value = "/{path}", method = RequestMethod.GET)
#RequestMapping(value = "/{path:[^/]+}", method = RequestMethod.GET)
which behave like /* (also matches the next /).
This issue is already haunting me for weeks and still no solution insight.
This whole question can also be seen as:
Why is "/[^/]+" not matching https://localhost:8443/whatever ?
Regex are usually not the fastest thing to try.
You can send a list of paths to
#RequestMApping(value={"", "/", "/test", "/api"}, method = RequestMethod.GET)
See Multiple Spring #RequestMapping annotations
I'm attempting to use WebJars-Locator with a Spring-Boot application to map JAR resources. As per their website, I created a RequestMapping like this:
#ResponseBody
#RequestMapping(method = RequestMethod.GET, value = "/webjars-locator/{webjar}/{partialPath:.+}")
public ResponseEntity<ClassPathResource> locateWebjarAsset(#PathVariable String webjar, #PathVariable String partialPath)
{
The problem with this is that the partialPath variable is supposed to include anything after the third slash. What it ends up doing, however, is limiting the mapping itself. This URI is mapped correctly:
http://localhost/webjars-locator/angular-bootstrap-datetimepicker/datetimepicker.js
But this one is not mapped to the handler at all and simply returns a 404:
http://localhost/webjars-locator/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css
The fundamental difference is simply the number of components in the path which should be handled by the regular expression (".+") but does not appear to be working when that portion has slashes.
If it helps, this is provided in the logs:
2015-03-03 23:03:53.588 INFO 15324 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/webjars-locator/{webjar}/{partialPath:.+}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity app.controllers.WebJarsLocatorController.locateWebjarAsset(java.lang.String,java.lang.String)
2
Is there some type of hidden setting in Spring-Boot to enable regular expression pattern matching on RequestMappings?
The original code in the docs wasn't prepared for the extra slashes, sorry for that!
Please try this code instead:
#ResponseBody
#RequestMapping(value="/webjarslocator/{webjar}/**", method=RequestMethod.GET)
public ResponseEntity<Resource> locateWebjarAsset(#PathVariable String webjar,
WebRequest request) {
try {
String mvcPrefix = "/webjarslocator/" + webjar + "/";
String mvcPath = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
String fullPath = assetLocator.getFullPath(webjar,
mvcPath.substring(mvcPrefix.length()));
ClassPathResource res = new ClassPathResource(fullPath);
long lastModified = res.lastModified();
if ((lastModified > 0) && request.checkNotModified(lastModified)) {
return null;
}
return new ResponseEntity<Resource>(res, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
I will also provide an update for webjar docs shortly.
Updated 2015/08/05: Added If-Modified-Since handling
It appears that you cannot have a PathVariable to match "the remaining part of the url". You have to use ant-style path patterns, i.e. "**" as described here:
Spring 3 RequestMapping: Get path value
You can then get the entire URL of the request object and extract the "remaining part".
I have a restful web service that's returning results like this:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Some Text</string>
However, the people on the receiving end need this text to be terminated w/ a special character such as "\r". How can I add that text to the end of my serialized response?
I'm sending this response from inside of a WCF service in C# like this:
[WebGet(UriTemplate = "/MyMethod?x={myId}"), OperationContract]
string GetSomeText(Guid myId);
I can think of three solutions:
1. Http Module (least code but most confusing for maintenance)
Assuming you're hosting your WCF in ASP.Net, you can create an Http module to add a \r to the end of all responses in your application.
This could be the code of the Http module. I've used 'End' as a suffix here because it's easier to read in a browser than \r, but for \r you would change the "End" in context_PostRequestHandlerExecute to "\r".
public class SuffixModule : IHttpModule
{
private HttpApplication _context;
public void Init(HttpApplication context)
{
_context = context;
_context.PostRequestHandlerExecute += context_PostRequestHandlerExecute;
}
void context_PostRequestHandlerExecute(object sender, EventArgs e)
{
// write the suffix if there is a body to this request
string contentLengthHeaderValue = _context.Response.Headers["Content-length"];
string suffix = "End";
if (!String.IsNullOrEmpty(contentLengthHeaderValue))
{
// Increase the content-length header by the length of the suffix
_context.Response.Headers["Content-length"] =
(int.Parse(contentLengthHeaderValue) + suffix.Length)
.ToString();
// and write the suffix!
_context.Response.Write(suffix);
}
}
public void Dispose()
{
// haven't worked out if I need to do anything here
}
}
Then you need to set up your module up in your web.config. The below assumes you have IIS running in Integrated Pipeline mode. If you haven't, you need to register the modules in the <system.web><httpModules> section.
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<!-- 'type' should be the fully-qualified name of the type,
followed by a comma and the name of the assembly-->
<add name="SuffixModule" type="WcfService1.SuffixModule,WcfService1"/>
</modules>
</system.webServer>
This option has the problems that it would affect all requests in your application by default and it would probably fall over if you decided to use chunked encoding.
2. Use ASP.NET MVC (changes technology but good maintainability)
Use MVC instead of WCF. You'd have far better control over your output.
3. Custom Serializer (lots of code, but less hacky than option 1)
You could write your own custom serializer. This StackOverflow question gives you pointers on how to do this. I didn't write a prototype for this because it looked as though there were many, many methods which needed to be overridden. I daresay most of them would be pretty simple delegations to the standard serializer.
I've tried to register the following ResourceMapping as an OSGi service:
package ru.focusmedia.odp.server.poim.http;
import org.ops4j.pax.web.extender.whiteboard.ResourceMapping;
import ru.focusmedia.odp.server.poim.api.PoimConfig;
import aQute.bnd.annotation.component.Component;
#Component(immediate = true)
public class PoimResourceMapping implements ResourceMapping {
private String httpContextId;
private String alias = "...";
private String someAbsolutePath = "...";
#Override
public String getHttpContextId() {
return httpContextId;
}
#Override
public String getAlias() {
return "/resources";
}
#Override
public String getPath() {
return someAbsolutePath;
}
}
but don't see the result in browser under http://127.0.0.1:8282/resources/aFileUnderMyPath. I can see that Pax Web is accessing my mapping in the log. Is this possible or do I need to write a servlet instead?
The short answer is no.
The path is resolved relative to the bundle and therefore your absolute path will turn in some url syntax error.
Why not loading your file in a bundle and serving it from there? We dynamically load bundles with resources that we need to serve following some versioning conventions.
I'm trying to implement simple web service client for PayPal Express Checkout API using JAX WS. PayPal Express Checkout API provides WSDL file, from which I was able to generate Java classes using CXF's wsdl2java utility.
From authentication reasons, it demands adding SOAP Header to each request. This header is quite simple and should look like here:
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECSOAPAPIBasics#id09C3I0CF0O6
Generated from WSDL classes include ebay.apis.eblbasecomponents.CustomSecurityHeaderType class which represents header which I need to add to each request.
So the question is: how can I add manually created instance of CustomSecurityHeaderType class to SOAP request's header taking into account following conditions:
I'm not very eager to use classes from com.sun.* package as mentioned in answer here: JAX-WS - Adding SOAP Headers (mainly because of possible portability issues between different JDK's)
I don't want to manually marshal that object into nested javax.xml.soap.SOAPElement instances as mentioned in answer here:
How do I add a SOAP Header using Java JAX-WS
So, it looks like I've found possible answer while combining JAX-WS & JAXB related answers from SO (I would really appreciate if somebody experienced in these technologies can check whether following is correct):
The obvious thing for me is to add SOAP message handler and alter header of SOAPMessage instance in it:
import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.soap.SOAPHeader;
import ebay.api.paypalapi.ObjectFactory; // class generated by wsdl2java
// following class is generated by wsdl2java utility Service class
final PayPalAPIInterfaceService payPalService = new PayPalAPIInterfaceService();
final PayPalAPIAAInterface expressCheckoutPort = payPalService.getPayPalAPIAA();
final Binding binding = ((BindingProvider) expressCheckoutPort).getBinding();
List<Handler> handlersList = new ArrayList<Handler>();
// now, adding instance of Handler to handlersList which should do our job:
// creating header instance
final CustomSecurityHeaderType headerObj = new CustomSecurityHeaderType();
final UserIdPasswordType credentials = new UserIdPasswordType();
credentials.setUsername("username");
credentials.setPassword("password");
credentials.setSignature("signature");
headerObj.setCredentials(credentials);
// bookmark #1 - please read explanation after code
final ObjectFactory objectFactory = new ObjectFactory();
// creating JAXBElement from headerObj
final JAXBElement<CustomSecurityHeaderType> requesterCredentials = objectFactory.createRequesterCredentials(headerObj);
handlersList.add(new SOAPHandler<SOAPMessageContext>() {
#Override
public boolean handleMessage(final SOAPMessageContext context) {
try {
// checking whether handled message is outbound one as per Martin Strauss answer
final Boolean outbound = (Boolean) context.get("javax.xml.ws.handler.message.outbound");
if (outbound != null && outbound) {
// obtaining marshaller which should marshal instance to xml
final Marshaller marshaller = JAXBContext.newInstance(CustomSecurityHeaderType.class).createMarshaller();
// adding header because otherwise it's null
final SOAPHeader soapHeader = context.getMessage().getSOAPPart().getEnvelope().addHeader();
// marshalling instance (appending) to SOAP header's xml node
marshaller.marshal(requesterCredentials, soapHeader);
}
} catch (final Exception e) {
throw new RuntimeException(e);
}
return true;
}
// ... default implementations of other methods go here
});
// as per Jean-Bernard Pellerin's comment setting handlerChain list here, after all handlers were added to list
binding.setHandlerChain(handlersList);
Explanation of bookmark #1:
one should marshal not the header object itself, but JAXBElement representing that object, because otherwise one will get an exception. One should use one of ObjectFactory classes which are generated from WSDL for creating needed JAXBElement instances from original objects.
(Thanks #skaffman for answer: No #XmlRootElement generated by JAXB )
One should also refer to Martin Straus answer which extends this one
This solution works great, but there's a catch. It generates this error when the inbound message is processed:
dic 19, 2012 7:00:55 PM com.sun.xml.messaging.saaj.soap.impl.EnvelopeImpl addHeader
SEVERE: SAAJ0120: no se puede agregar una cabecera si ya hay una
Exception in thread "main" javax.xml.ws.WebServiceException: java.lang.RuntimeException: com.sun.xml.messaging.saaj.SOAPExceptionImpl: Can't add a header when one is already present.
at com.sun.xml.ws.handler.ClientSOAPHandlerTube.callHandlersOnResponse(ClientSOAPHandlerTube.java:167)
at com.sun.xml.ws.handler.HandlerTube.processResponse(HandlerTube.java:174)
at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:1074)
at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:979)
at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:950)
at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:825)
at com.sun.xml.ws.client.Stub.process(Stub.java:443)
at com.sun.xml.ws.client.sei.SEIStub.doProcess(SEIStub.java:174)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:119)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:102)
at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:154)
at $Proxy38.wsRdyCrearTicketDA(Unknown Source)
at ar.com.fit.fides.remedy.api.ws.ServicioCreacionTickets.crearTicket(ServicioCreacionTickets.java:55)
at ar.com.fit.fides.remedy.api.ws.ConectorRemedyWS.crearTicket(ConectorRemedyWS.java:43)
at ar.com.fit.fides.remedy.api.ws.ConectorRemedyWS.main(ConectorRemedyWS.java:90)
Caused by: java.lang.RuntimeException: com.sun.xml.messaging.saaj.SOAPExceptionImpl: Can't add a header when one is already present.
at ar.com.fit.fides.remedy.api.ws.AuthenticationHandler.handleMessage(AuthenticationHandler.java:50)
at ar.com.fit.fides.remedy.api.ws.AuthenticationHandler.handleMessage(AuthenticationHandler.java:23)
at com.sun.xml.ws.handler.HandlerProcessor.callHandleMessageReverse(HandlerProcessor.java:341)
at com.sun.xml.ws.handler.HandlerProcessor.callHandlersResponse(HandlerProcessor.java:214)
at com.sun.xml.ws.handler.ClientSOAPHandlerTube.callHandlersOnResponse(ClientSOAPHandlerTube.java:161)
... 14 more
Caused by: com.sun.xml.messaging.saaj.SOAPExceptionImpl: Can't add a header when one is already present.
at com.sun.xml.messaging.saaj.soap.impl.EnvelopeImpl.addHeader(EnvelopeImpl.java:128)
at com.sun.xml.messaging.saaj.soap.impl.EnvelopeImpl.addHeader(EnvelopeImpl.java:108)
at ar.com.fit.fides.remedy.api.ws.AuthenticationHandler.handleMessage(AuthenticationHandler.java:45)
So, the solution is to check whether the message being handled if the outbound message, like this:
public boolean handleMessage(SOAPMessageContext context) {
try {
Boolean outbound = (Boolean) context.get("javax.xml.ws.handler.message.outbound");
if (outbound != null && outbound) {
// obtaining marshaller which should marshal instance to xml
final Marshaller marshaller = JAXBContext.newInstance(AuthenticationInfo.class).createMarshaller();
// adding header because otherwise it's null
final SOAPHeader soapHeader = context.getMessage().getSOAPPart().getEnvelope().addHeader();
// marshalling instance (appending) to SOAP header's xml node
marshaller.marshal(info, soapHeader);
}
} catch (final Exception e) {
throw new RuntimeException(e);
}
return true;
}
I created a web service exposing method with params user and password as header like this:
#WebService(serviceName="authentication")
public class WSAuthentication {
String name = null;
String password = null;
public WSAuthentication() {
super();
}
public WSAuthentication(String name, String password) {
this.name = name;
this.password = password;
}
private static String getData(WSAuthentication sec) {
System.out.println("********************* AUTHENTICATION ********************" + "\n" +
"**********USER: " + sec.name + "\n" +
"******PASSWORD: " + sec.password + "\n" +
"******************************** AUTHENTICATION ****************************");
return sec.name + " -- " + sec.password;
}
#WebMethod(operationName="security", action="authenticate")
#WebResult(name="answer")
public String security(#WebParam(header=true, mode=Mode.IN, name="user") String user, #WebParam(header=true, mode=Mode.IN, name="password") String password) {
WSAuthentication secure = new WSAuthentication(user, password);
return getData(secure);
}
}
Try compiling it and testing generated from WSDL class. I hope this helps.
I found this answer:
JAX-WS - Adding SOAP Headers
Basically you add -XadditionalHeaders to the compiler options and objects in the headers also appear in your generated code as parameters of the method.
If you are using maven, and the jaxws-maven-plugin all you have to do is add the xadditionalHeaders flag to true and the client will be generated with the methods that have the headers as input.
https://jax-ws-commons.java.net/jaxws-maven-plugin/wsimport-mojo.html#xadditionalHeaders