How to combine Jetty with embedding ServletContextHandler and AbstractHandler? - java

Is it possible to realize Jetty server by java that will listening to different ports and has two different handlers (ServletContextHandler/extends AbstractHandler)?
I have json request/response service that work on port 8899 (extends AbstractHandler) and webapp that work on port 8080 (ServletContextHandler) at one Jetty server.
Here is ServletContextHandler, listening 8080:
private static ServletContextHandler getServletContextHandler() throws IOException {
WebAppContext contextHandler = new WebAppContext();
contextHandler.setErrorHandler(null);
contextHandler.setContextPath(CONTEXT_PATH);
WebApplicationContext context = getContext();
contextHandler.addServlet(new ServletHolder(new DispatcherServlet(context)), CONTEXT_PATH);
contextHandler.addEventListener(new ContextLoaderListener(context));
contextHandler.setResourceBase(new ClassPathResource("webapp").getURI().toString());
contextHandler.setDescriptor("/webapp/WEB-INF/webstat.xml");
return contextHandler;
}
private static WebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("webapp/WEB-INF/");
log.info("CONTEXT name {} locations {}", context.getDisplayName(), context.getConfigLocations());
return context;
}
Handler that listening 8899:
private class Handler extends AbstractHandler {
public void handle(String target,
Request baseRequest,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
String contentEncoding = request.getHeader(RpcClient.CONTENT_ENCODING);
String contentType = request.getHeader(RpcClient.CONTENT_TYPE);
if ((contentEncoding != null && contentEncoding.startsWith(RpcClient.APPLICATION_JSON))||
contentType.equals(RpcClient.APPLICATION_JSON)) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
response.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
}
baseRequest.setHandled(true);
}
}
And finally method that realize Jetty server:
public void init() {
try {
Server httpServer = new Server();
ServerConnector rpcConnector = new ServerConnector(httpServer);
rpcConnector.setHost("localhost");
rpcConnector.setPort(8899);
rpcConnector.setIdleTimeout(30000);
ServerConnector httpConnector = new ServerConnector(httpServer);
httpConnector.setHost("localhost");
httpConnector.setPort(8080);
httpConnector.setIdleTimeout(30000);
httpServer.setConnectors(new Connector[] {rpcConnector, httpConnector});
HandlerList handlerList = new HandlerList();
handlerList.setHandlers(new Handler[] {new Handler() }, new ContextHandler(getServletContextHandler()) {});//CAN'T APPLY them in HandlerList
httpServer.setHandler(handlerList);
httpServer.setStopAtShutdown(true);
httpServer.start();
log.info("WebServer start ...");
httpServer.join();
} catch (Exception ex) {
log.error("Exception in http server. Exception: {}", ex.getMessage());
}
}
I've check ContextHandlerCollection, HandlerList, HandlerCollection & etc.
The goal is to combine two different services for work at one jetty server. Logic of there work is that user go to http://localhost:8080/getStatistics, spring mvc send json request to localhost:8899, application collect statistics, and response it by json to spring mvc and it'll generate jsp with statistics to user.
Is any idea how to combine those Handlers?

Related

Embedded Jetty HttpServer not accepting mutipart/form-data

I'm working to migrate from Eclipse Jersey/Grizzly (2.33) to Eclipse/Jetty (10.0.6) for the embedded Http Server for our REST API, and I can't for the life of me get the multipart/form-data uploads working properly. I freely admit I am not versed in Jetty configuration, nor Jersey/Grizzly configuration, and I'm cobbling together the old code with the bare minimum of boilerplate from Jetty cookbooks.
At this point, I'd be thrilled to just get the server to accept the request. I can work on how to handle the files on my own. My primary goal at the moment is to not have to rewrite dozens of servlets/handlers right now (hence the use of the Jersey ServletContainer).
This is the server code:
public static void start() throws Exception {
httpConfig = new HttpConfiguration();
HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
server = new Server();
ServerConnector connector = new ServerConnector(server, http11);
connector.setPort((cmdOptions.port < 0 ? 9998 : cmdOptions.port));
server.setConnectors(new Connector[] {connector});
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
HandlerList handlers = new HandlerList();
ServletHandler servletHandler = new ServletHandler();
// Set up the resources.common package as the handlers for the servlet
ServletHolder servletHolder = context.addServlet(ServletContainer.class, "/*");
servletHolder.setInitOrder(0);
servletHolder.setInitParameter("jersey.config.server.provider.packages", "resources.grizzly;resources.common");
servletHandler.addServlet(servletHolder);
// MultiPartConfig setup - to allow for ServletRequest.getParts() usage
Path multipartTmpDir = Paths.get("target", "multipart-tmp");
multipartTmpDir = CommonResFileManager.ensureDirExists(multipartTmpDir);
String location = multipartTmpDir.toString();
long maxFileSize = 10 * 1024 * 1024; // 10 MB
long maxRequestSize = 10 * 1024 * 1024; // 10 MB
int fileSizeThreshold = 64 * 1024; // 64 KB
MultipartConfigElement multipartConfig = new MultipartConfigElement(location, maxFileSize, maxRequestSize, fileSizeThreshold);
FilterHolder filterHolder;
filterHolder = context.addFilter(resources.jetty.SecurityFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
filterHolder.setAsyncSupported(true);
CorsHandler corsHandler = new CorsHandler();
corsHandler.setHandler(context);
UploadHandler uploadHandler = new UploadHandler("/G/uploadFolder", multipartConfig, multipartTmpDir);
handlers.addHandler(corsHandler);
handlers.addHandler(uploadHandler);
handlers.addHandler(servletHandler);
server.setHandler(handlers);
server.start();
}
The resources of interest are:
public class CommonResProject extends CommonResBase {
...
#POST #Path("uploadFolder")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.MULTIPART_FORM_DATA)
public String uploadFolder(final FormDataMultiPart multiPart)
{
Collection<Part> parts = null;
try {
parts = ((HttpServletRequest)request).getParts();
} catch (IOException | ServletException ex) {
Logger.getLogger(CommonResProject.class.getName()).log(Level.SEVERE, null, ex);
}
if(parts != null){
parts.stream().forEach(p -> System.out.println(p.getName() + " ["+p.getContentType()+"]: "+p.getSize()+" bytes"));
}
// projects is a POJO that actually does the fiddly bits with the uploaded files
boolean retVal = projects.uploadFolder(getDB(), getUserId(), multiPart);
return "{\"retVal\" : " + String.valueOf(retVal) + "}";
}
...
Which is extended by:
#Path("/GProject")
public class GProject extends CommonResProject
{
public GProject()
{
super();
resInterface = new GBaseRes(); // Must always do
}
public static void processParts(HttpServletRequest request, HttpServletResponse response, java.nio.file.Path outputDir) throws ServletException, IOException
{
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
for (Part part : request.getParts())
{
out.printf("Got Part[%s].size=%s%n", part.getName(), part.getSize());
out.printf("Got Part[%s].contentType=%s%n", part.getName(), part.getContentType());
out.printf("Got Part[%s].submittedFileName=%s%n", part.getName(), part.getSubmittedFileName());
String filename = part.getSubmittedFileName();
if (StringUtil.isNotBlank(filename))
{
// ensure we don't have "/" and ".." in the raw form.
filename = URLEncoder.encode(filename, "utf-8");
java.nio.file.Path outputFile = outputDir.resolve(filename);
try (InputStream inputStream = part.getInputStream();
OutputStream outputStream = Files.newOutputStream(outputFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))
{
IO.copy(inputStream, outputStream);
out.printf("Saved Part[%s] to %s%n", part.getName(), outputFile.toString());
}
}
}
}
public static ServletContextHandler newServletUploadHandler(MultipartConfigElement multipartConfig, java.nio.file.Path outputDir) throws IOException
{
ServletContextHandler context = new ServletContextHandler();
SaveUploadServlet saveUploadServlet = new SaveUploadServlet(outputDir);
ServletHolder servletHolder = new ServletHolder(saveUploadServlet);
servletHolder.getRegistration().setMultipartConfig(multipartConfig);
context.addServlet(servletHolder, "/uploadFolder");
return context;
}
public static class SaveUploadServlet extends HttpServlet
{
private final java.nio.file.Path outputDir;
public SaveUploadServlet(java.nio.file.Path outputDir) throws IOException
{
this.outputDir = outputDir.resolve("servlet");
ensureDirExists(this.outputDir);
}
#Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
processParts(request, response, outputDir);
}
}
public static class UploadHandler extends AbstractHandler
{
private final String contextPath;
private final MultipartConfigElement multipartConfig;
private final java.nio.file.Path outputDir;
public UploadHandler(String contextPath, MultipartConfigElement multipartConfig, java.nio.file.Path outputDir) throws IOException
{
super();
this.contextPath = contextPath;
this.multipartConfig = multipartConfig;
this.outputDir = outputDir.resolve("handler");
CommonResFileManager.ensureDirExists(this.outputDir);
}
#Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (!target.startsWith(contextPath))
{
// not meant for us, skip it.
return;
}
if (!request.getMethod().equalsIgnoreCase("POST"))
{
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
return;
}
// Ensure request knows about MultiPartConfigElement setup.
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, multipartConfig);
// Process the request
processParts(request, response, outputDir);
//baseRequest.setHandled(true);
}
}
}
And the whole thing generates the following stacktrace when I try to upload a set of files:
2021-09-13 12:58:17 SEVERE - resources.common.ResponseExceptionMapper toResponse -- HTTP 415 Unsupported Media Type
javax.ws.rs.NotSupportedException: HTTP 415 Unsupported Media Type
at org.glassfish.jersey.server.spi.internal.ParameterValueHelper.getParameterValues(ParameterValueHelper.java:75)
at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$AbstractMethodParamInvoker.getParamValues(JavaResourceMethodDispatcherProvider.java:109)
at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:219)
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:79)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:475)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:397)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:255)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:680)
at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:394)
at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:346)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:366)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:319)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:205)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:764)
at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1619)
at resources.jetty.SecurityFilter.doFilter(SecurityFilter.java:232)
at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202)
at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:164)
at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202)
at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1594)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:506)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1571)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1372)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:463)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1544)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1294)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
at resources.jetty.CorsHandler.handle(CorsHandler.java:30)
at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
at org.eclipse.jetty.server.Server.handle(Server.java:562)
at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:406)
at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:663)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:398)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:319)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
at org.eclipse.jetty.io.SocketChannelEndPoint$1.run(SocketChannelEndPoint.java:101)
at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:412)
at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:381)
at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:268)
at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0(AdaptiveExecutionStrategy.java:138)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:378)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=multipart/form-data;boundary=----WebKitFormBoundary4K5nPFIDDwLPZAnk, type=class org.glassfish.jersey.media.multipart.FormDataMultiPart, genericType=class org.glassfish.jersey.media.multipart.FormDataMultiPart.
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:208)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:132)
at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundReadFrom(MappableExceptionWrapperInterceptor.java:49)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:132)
at org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1072)
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:885)
at org.glassfish.jersey.server.ContainerRequest.readEntity(ContainerRequest.java:282)
at org.glassfish.jersey.server.internal.inject.EntityParamValueParamProvider$EntityValueSupplier.apply(EntityParamValueParamProvider.java:73)
at org.glassfish.jersey.server.internal.inject.EntityParamValueParamProvider$EntityValueSupplier.apply(EntityParamValueParamProvider.java:56)
at org.glassfish.jersey.server.spi.internal.ParamValueFactoryWithSource.apply(ParamValueFactoryWithSource.java:50)
at org.glassfish.jersey.server.spi.internal.ParameterValueHelper.getParameterValues(ParameterValueHelper.java:68)
First, don't use ServletHandler directly like that.
Only use ServletContetHandler and ServletHolder to configure what you need.
ServletHandler is an internal class not meant to be used directly like that.
Especially with all of the configuration you are attempting to force on it.
Next, convert UploadHandler to a normal/formal HttpServlet and add it to the ServletContextHandler properly (you can even use the same url-pattern as you are currently). The ServletContext is important here (for multipart), and your raw/naked UploadHandler is not actually handling multipart like you think it is.
The stacktrace indicates that you are not using Jetty for multipart at the point in time where the stacktrace is generated, which means it bypassed the UploadHandler and Jersey itself is attempting to handle the multipart content. This probably means you have the specify the MultiPartConfigElement on the Jersey servlet instead.
ServletHolder servletHolder = context.addServlet(ServletContainer.class, "/*");
servletHolder.setInitOrder(0);
servletHolder.setInitParameter("jersey.config.server.provider.packages",
"resources.grizzly;resources.common");
Path multipartTmpDir = Paths.get("target", "multipart-tmp");
multipartTmpDir = CommonResFileManager.ensureDirExists(multipartTmpDir);
String location = multipartTmpDir.toString();
long maxFileSize = 10 * 1024 * 1024; // 10 MB
long maxRequestSize = 10 * 1024 * 1024; // 10 MB
int fileSizeThreshold = 64 * 1024; // 64 KB
MultipartConfigElement multipartConfig = new MultipartConfigElement(location,
maxFileSize, maxRequestSize, fileSizeThreshold);
servletHolder.getRegistration().setMultipartConfig(multipartConfig);

How to enable only http on some endpoints and https on some other endpoints in a spring boot rest controller?

I want to enable http on some endpoints and https on another set of endpoints.
I got solutions like configure https through application.properties and http by programmatically creating an extra connector, but all the results enable both http and https for all endpoints.
Can someone let me know how to configure some endpoints with https and some end points with http?
I figured this out for Jetty servlet which I use. If you use the default TomCat servlet you will have to do something similar that works for TomCat I suppose.
So to start with I have a ssl port as default that is activated. To also allow http you need to configure an additional http port in your config. Then you need to add a server Handler. You could add the Handler SecuredRedirectHandler to redirect ALL http requests to the https port. Since we don't want to redirect ALL http requests we make our own CustomRedirectHandler that extends SecuredRedirectHandler.
#Bean
public ConfigurableServletWebServerFactory webServerFactory() {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.addServerCustomizers(new JettyServerCustomizer() {
#Override
public void customize(Server server) {
final HttpConnectionFactory httpConnectionFactory = server.getConnectors()[0].getConnectionFactory(HttpConnectionFactory.class);
// Enable HTTP for assigned port
final ServerConnector httpConnector = new ServerConnector(server, httpConnectionFactory);
httpConnector.setPort(serverProperties.intHttpPort() /* HTTP */);
server.addConnector(httpConnector);
// Add a CustomRedirectHandler to Server Handlers
final HandlerList handlerList = new HandlerList();
handlerList.addHandler(new CustomRedirectHandler());
for(Handler handler : server.getHandlers()) {
handlerList.addHandler(handler);
}
server.setHandler(handlerList);
}
});
return factory;
}
In our CustomRedirectHandler we can check if the requested endpoint is in our "allowed http" array. If it already request https or is allowed http then we do nothing, else redirect to https. My example allows http only for the endpoint that starts with "/.well-known/acme-challenge/" to allow requests to http://example.com/.well-known/acme-challenge/TOKEN for example.
public class CustomRedirectHandler extends SecuredRedirectHandler {
private final String[] allowedHttp = {"/.well-known/acme-challenge/"};
#Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
HttpChannel channel = baseRequest.getHttpChannel();
if (baseRequest.isSecure() || channel == null) {
// nothing to do, already requested https
return;
}
// Check if request is for allowed http
if (allowHttp(baseRequest)) {
return;
}
// Else Redirect to https
super.handle(target, baseRequest, request, response);
}
public boolean allowHttp(Request baseRequest) {
String pathInfo = baseRequest.getPathInfo();
if (pathInfo == null) {
return false;
}
for (String allowed : allowedHttp) {
if (pathInfo.startsWith(allowed)) {
return true;
}
}
return false;
}
}

Simple rest with undertow

I have this code for server:
Undertow server = Undertow.builder()
.addHttpListener(8080, "localhost")
.setHandler(Handlers.path()
.addPrefixPath("/item", new ItemHandler())
)
.build();
server.start();
And handler:
private class ItemHandler implements HttpHandler {
#Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
exchange.getPathParameters(); // always null
//ItemModel item = new ItemModel(1);
//exchange.getResponseSender().send(mapper.writeValueAsString(item));
}
}
I want to send request /item/10 and get 10 in my parameter. How to specify path and get it?
You need a PathTemplateHandler and not a PathHandler, see:
Undertow server = Undertow.builder()
.addHttpListener(8080, "0.0.0.0")
.setHandler(Handlers.pathTemplate()
.add("/item/{itemId}", new ItemHandler())
)
.build();
server.start();
Then, in your ItemHandler:
class ItemHandler implements HttpHandler {
#Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
// Method 1
PathTemplateMatch pathMatch = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY);
String itemId1 = pathMatch.getParameters().get("itemId");
// Method 2
String itemId2 = exchange.getQueryParameters().get("itemId").getFirst();
}
}
The method 2 works because Undertow merges parameters in the path with the query parameters by default.
If you do not want this behavior, you can use instead:
Handlers.pathTemplate(false)
The same applies to the RoutingHandler, this is probably what you want to use eventually to handle multiple paths.
Handlers.rounting() or Handlers.routing(false)

Embedded Jetty web server not working, handler not invoked

I'm trying to embed Jetty 8 (8.1.18.v20150929) into a Java (jdk1.7.0_67) application. I have the following code:
public static final String HTTP_PATH = "/session";
public static final int HTTP_PORT = 9995;
// Open the HTTP server for listening to requests.
logger.info("Starting HTTP server, Port: " + HTTP_PORT + ", Path: "
+ "/session");
httpServer = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(HTTP_PORT);
connector.setHost("localhost");
httpServer.addConnector(connector);
TestHttpHandler handler = new TestHttpHandler(this);
ContextHandler ch = new ContextHandler();
ch.setContextPath(HTTP_PATH);
ch.setHandler(handler);
httpServer.setHandler(ch);
try {
httpServer.start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
My handler is pretty basic as a test:
public void handle(String target, Request baseRequest,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
logger.debug("Handling");
}
If I run the app and then use CURL to send a GET request to http://localhost:9995/session, then it returns a 200 status but there's no debug output.
If I access http://localhost:9995/session2, I get a 404 error.
I've read many examples online but for some reason I can't seem to get the handler to work properly. Am I doing something wrong? Thanks
I had exactly the same problem, and this is just a misunderstanding about how the Jetty API works. I was expecting to use ContextHandlers as a minimal implementation of REST services, however ContextHandlers are meant to handle requests to an entire context base (for example http://server:80/context-base, i.e. the equivalent of an app in Tomcat). The correct way to solve this question is to use Servlets:
Server server = new Server(9995);
ServletContextHandler root = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
root.setContextPath("/");
ServletHolder holder = new ServletHolder(new HttpServlet() {
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
logger.debug("Handling");
}
});
server.start();
server.join();

sending request to the newly created jetty server for testing purposes

I'm writing integration JUnit test. My task is to test whether the response of my local server is correct. The mentioned server takes as a GET parameter an address of page to be analysed (for example: localhost:8000/test?url=http://www.example.com).
To avoid being dependent on www.example.com I want to start for this particular test my own jetty server, which always serves the same content.
private static class MockPageHandler extends AbstractHandler {
public void handle(String target,Request baseRequest, HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
final String responseString = loadResource("index.html");
response.getWriter().write(responseString);
baseRequest.setHandled(true);
}
}
public void test() throws Exception {
final int PORT = 8080;
final Server server = new Server(PORT);
server.setHandler(new MockPageHandler());
server.start();
final ContentResponse response =
client.newRequest("http://localhost:8000/test?url=http://localhost:8080").send();
/* some assertions. */
server.stop();
server.join();
}
Every time I execute this test, the handle method in MockPageHandler is never invoked.
Do you have any suggestions why this not works?
P.S. When I remove server.stop() and in browser type http://localhost:8080 the proper page is shown.
Quick answer:
Remove the server.join() line. That line makes the junit thread wait until the server thread stops. Which is not needed for unit testing.
Long answer:
What we (the jetty developers) have learned about using jetty embedded servers with junit.
Use the #Before and #After annotations to start and stop the server if you have 1 test method, or some requirement that the server be pristine between test methods.
Example #Before / #After (Jetty 9.x):
public class MyTest
{
private Server server;
private URI serverUri;
#Before
public void startServer() throws Exception
{
this.server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0); // let connector pick an unused port #
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
// Serve capture servlet
context.addServlet(new ServletHolder(new MyServlet()),"/my/*");
// Start Server
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
this.serverUri = new URI(String.format("http://%s:%d/",host,port));
}
#After
public void stopServer()
{
try
{
server.stop();
}
catch (Exception e)
{
e.printStackTrace(System.err);
}
}
#Test
public void testMe()
{
// Issue request to server
URI requestUri = serverUri.resolve("/my/test");
// assert the response
}
}
This technique makes the server start on port 0, which is a magic number that tells the underlying stack to pick an empty port and start listening. The test case then asks the server what port number it is listening on and builds out the serverUri field to be appropropriate for this test run.
This technique works great, however, it will start/stop the server for each method.
Enter, the better technique, use the #BeforeClass and #AfterClass annotations to start/stop the server once for the entire test class, running all of the methods inside of the test class against this started server.
Example #BeforeClass / #AfterClass (Jetty 9.x):
public class MyTest
{
private static Server server;
private static URI serverUri;
#BeforeClass
public static void startServer() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0); // let connector pick an unused port #
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
// Serve capture servlet
context.addServlet(new ServletHolder(new MyServlet()),"/my/*");
// Start Server
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
serverUri = new URI(String.format("http://%s:%d/",host,port));
}
#AfterClass
public static void stopServer()
{
try
{
server.stop();
}
catch (Exception e)
{
e.printStackTrace(System.err);
}
}
#Test
public void testMe()
{
// Issue request to server
URI requestUri = serverUri.resolve("/my/test");
// assert the response
}
}
Doesn't look much different? Yes, the changes are subtle. #Before became #BeforeClass, #After became #AfterClass. The start/stop methods are now static. The server and serverUri fields are now static.
This technique is used where we have dozens of test methods that access the same server, and those requests do not alter the state in the server. This speeds up the test case execution by simply not recreating the server between each test method.
Give a try to "com.jayway.restassured" for your http test. too easy to write some test :
#Test
public void testNotGetAll() {
expect().
statusCode(404).
when().
get(baseUrl+"/games/");
}
this method call "http://mywebserver.local:8080/rest/games/" and verify that a 404 http status code is returned.
And this approach synchronised with a Jetty server (for example) started at pre-integration-test in the maven lifecycle, you match the perfect mix to process integration test !

Categories