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);
Related
I recently followed this blog to integrate swagger in my embedded jetty project but after running, I'm not able to access the swagger.json file on any path combination. Accessing the servlets for the resources work with no error but I get the following errors when I try to get the swagger.json file
http://host:7000/swagger-core ===> HTTP ERROR 405
http://host:7000/swagger-core/swagger.json ===> HTTP ERROR 404
http://host:7000/user/swagger.json ===> HTTP ProfileServlet response, not swagger.json
http://host:7000/user ===> HTTP ProfileServlet response, not swagger.json
http://host:7000/swagger.json ===> HTTP ERROR 404
http://host:7000/api/swagger.json ===> HTTP ERROR 404
http://host:7000/ ===> Static swagger sample page (Pet store), not swagger.json
Main.java
public static void main(String[] args) throws Exception {
Server server = initializeApi(properties);
server.start();
logger.info("Api resource service started");
server.join();
}
private static Server initializeApi(Properties properties) {
logger.info("Initializing user profile server...");
new UserDao();
Server server = new Server(Integer.parseInt(properties.getProperty(Config.JETTY_SERVICE_PORT)));
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
servletContextHandler.setContextPath("/");
server.setHandler(servletContextHandler);
//Setup APIs
ServletHolder apiservlet = servletContextHandler.addServlet(ProfileServlet.class, "/user/*");
apiservlet.setInitOrder(1);
apiservlet.setInitParameter("com.sun.jersey.config.property.packages", "com.api.resources;io.swagger.jaxrs.json;io.swagger.jaxrs.listing");
logger.info("User profile server initialized.");
// Setup Swagger servlet
ServletHolder swaggerServlet = servletContextHandler.addServlet(DefaultJaxrsConfig.class, "/swagger-core");
swaggerServlet.setInitOrder(2);
swaggerServlet.setInitParameter("api.version", "1.0.0");
// Setup Swagger-UI static resources
String resourceBasePath = Main.class.getResource("/webapp").toExternalForm();
servletContextHandler.setWelcomeFiles(new String[] {"index.html"});
servletContextHandler.setResourceBase(resourceBasePath);
servletContextHandler.addServlet(new ServletHolder(new DefaultServlet()), "/*");
return server;
}
}
ProfileServlet.java
#SwaggerDefinition(
info = #Info(
title = "User Profile Servlet",
version = "1.0.0",
description = "Servlet that handles basic CRUD operations to the user profile data source",
contact = #Contact(name = "XYZ", email = "XYZ", url = "XYZ"),
termsOfService = "XYZ",
license = #License(name = "XYZ", url = "XYZ")
),
basePath = "/",
consumes = {"application/json"},
produces = {"application/json"},
schemes = {SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS},
tags = {#Tag(name = "users", description = "CRUD operations on user datatype")}
)
#Api(value = "/user", description = "performs CRUD operations on a user profile")
public class ProfileServlet extends HttpServlet {
Logger logger = Logger.getLogger(ProfileServlet.class.getSimpleName());
public ProfileServlet(){
}
#ApiOperation(httpMethod = "GET", value = "Returns a list of the user profile datatype", notes = "", response = UserDatatype.class, nickname = "getUser", tags = ("User"))
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Succssful retrieval of user profiles", response = UserDatatype.class),
#ApiResponse(code = 500, message = "Internal server error")
})
#ApiImplicitParams({
#ApiImplicitParam(name = "id", value = "profile id", required = false, dataType = "String", paramType = "query"),
#ApiImplicitParam(name = "firstname", value = "First name of user", required = false, dataType = "String", paramType = "query"),
#ApiImplicitParam(name = "lastname", value = "Last name of user", required = false, dataType = "String", paramType = "query"),
#ApiImplicitParam(name = "phone", value = "phone number of user", required = false, dataType = "String", paramType = "query"),
#ApiImplicitParam(name = "signup", value = "Sign up date of user, in dd-MM-yyyy forma", required = false, dataType = "java.sql.Date", paramType = "query")
})
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RpcLogTemplate logTemplate = new RpcLogTemplate(req.getRemoteHost(),req.getParameter("client"), req.getParameter("clientapp"), Config.localhost, Config.SERVICE_INSTANCE, Config.SERVICE_APP, req.getParameterMap(), new Date().getTime() );
logger.debug("Received request: GET");
handleGet(req, resp, logTemplate);
logTemplate.setResponseTimestamp(new Date().getTime());
//LoggerService.INSTANCE.addLog(logTemplate);
}
private void handleGet(HttpServletRequest request, HttpServletResponse response, RpcLogTemplate logTemplate) throws IOException {
Gson gson = new Gson();
String param = null;
param = request.getParameter("id");
if(param!= null){
logger.info("Query by ID received. All other params would be ignored");
UserDatatype userDatatype = UserDao.INSTANCE.findById(param);
if(userDatatype == null){
response.setStatus(HttpServletResponse.SC_OK);
logger.info("Null object returned");
return;
}else{
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter printWriter = response.getWriter();
printWriter.write(gson.toJson(userDatatype, UserDatatype.class));
printWriter.flush();
printWriter.close();
}
}else{
Map<String, String> queryString = new HashMap<>();
//TODO: optimize this
param = request.getParameter("firstname");
if(param != null)
queryString.put("firstname", param);
param = request.getParameter("lastname");
if(param != null)
queryString.put("lastname", param);
param = request.getParameter("phone");
if(param != null)
queryString.put("phone", param);
param = request.getParameter("signup");
if(param != null)
queryString.put("signup", param);
UserDatatype[] userDatatypes = UserDao.INSTANCE.findByParams(queryString);
if(userDatatypes == null){
response.setStatus(HttpServletResponse.SC_OK);
logger.info("Null object returned");
return;
}else{
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter printWriter = response.getWriter();
printWriter.write(gson.toJson(userDatatypes, UserDatatype[].class));
printWriter.flush();
printWriter.close();
}
}
}
}
Bootstrap.java
public class Bootstrap extends HttpServlet {
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.2");
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setHost("localhost:7000");
beanConfig.setBasePath("/");
beanConfig.setResourcePackage("io.swagger.resources");
beanConfig.setScan(true);
beanConfig.setPrettyPrint(true);
}
}
All help appreciated.
EDIT: Quick update. After modifying the initializeAPI method to the original Servlet class in the blog(see below), I was able to get some response from the swagger-ui on http://host:7000/api/swagger.json. But I it seems like swagger wasnt able to parse my servlet annotations
http://host:7000/api/swagger.json ===> {"swagger":"2.0","info":{"version":"1.0.0","title":""}}
UPDATED CODE
private static Server initializeApi(Properties properties) {
logger.info("Initializing user profile server...");
new UserDao();
Server server = new Server(Integer.parseInt(properties.getProperty(Config.JETTY_SERVICE_PORT)));
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
servletContextHandler.setContextPath("/");
server.setHandler(servletContextHandler);
//Setup APIs
ServletHolder apiservlet = servletContextHandler.addServlet(ServletContainer.class, "/api/*");
apiservlet.setInitOrder(1);
apiservlet.setInitParameter("com.sun.jersey.config.property.packages", "com.api.resources;io.swagger.jaxrs.json;io.swagger.jaxrs.listing");
apiservlet = servletContextHandler.addServlet(ProfileServlet.class, "/user/*");
//apiservlet.setInitOrder(1);
apiservlet.setInitParameter("com.sun.jersey.config.property.packages", "com.api.resources;io.swagger.jaxrs.json;io.swagger.jaxrs.listing");
logger.info("User profile server initialized.");
// Setup Swagger servlet
ServletHolder swaggerServlet = servletContextHandler.addServlet(DefaultJaxrsConfig.class, "/swagger-core");
swaggerServlet.setInitOrder(2);
swaggerServlet.setInitParameter("api.version", "1.0.0");
// Setup Swagger-UI static resources
String resourceBasePath = Main.class.getResource("/webapp").toExternalForm();
servletContextHandler.setWelcomeFiles(new String[] {"index.html"});
servletContextHandler.setResourceBase(resourceBasePath);
servletContextHandler.addServlet(new ServletHolder(new DefaultServlet()), "/*");
return server;
}
So thanks to the Swagger google group who figured out what was wrong with my code. Two things:
1.You have to add your package (the one that contains the servlets as an initialization parameter to the ServletContainer class. So under Main.class in the initializeApi() method, I changed it from:
//Setup APIs
ServletHolder apiservlet = servletContextHandler.addServlet(ServletContainer.class, "/api/*");
apiservlet.setInitOrder(1); apiservlet.setInitParameter("com.sun.jersey.config.property.packages", "com.api.resources;io.swagger.jaxrs.json;io.swagger.jaxrs.listing");
to this:
//Setup APIs
ServletHolder apiservlet = servletContextHandler.addServlet(ServletContainer.class, "/api/*");
apiservlet.setInitOrder(1);
apiservlet.setInitParameter("com.sun.jersey.config.property.packages", "com.api.resources;io.swagger.jaxrs.json;io.swagger.jaxrs.listing; {path/to/package}");
In my case {path/to/package} was com.coreservice.servlets
2.Add your package name as a resource to the Swagger initialization servlet. In my project, it was the Bootstrap.java class. So my new Bootstrap.java class looks like this
public class Bootstrap extends HttpServlet {
#Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.2");
beanConfig.setSchemes(new String[]{"http"});
beanConfig.setHost("localhost:7000");
beanConfig.setBasePath("/");
beanConfig.setResourcePackage("com.coreservice.servlets");
beanConfig.setScan(true);
beanConfig.setPrettyPrint(true);
}
In a Java HttpServlet, is it possible to request data from another local service using the original request's header information without necessarily forwarding?
For example, I have FooBar.java:
// Handles the url at /foo/bar and can be accessed at http://localhost/foo/bar
public class FooBar extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Object data = ... // 1. Retrieve data at http://localhost/foo/baz utilizing the current request's header
Object newData = doSomething(data); // 2. Process the data
response.getWriter().write(newData.toString); // 3. Return the processed data
}
private Object doSomething(Object data)
{
// Perform some business logic
}
}
Step 1 is the issue here. The purpose of this is that I want to be able to perform some sort of logic on the data before returning it in full, but don't necessarily have access do make the changes on the handler at /foo/baz do to the propriety nature of things.
You can use this answer of me to create a HTTP Request: send get request
In addition, it may be necessary to copy the request header with some care:
private static final Set forbiddenCopyHeaders = new HashSet<>(Arrays.asList(new String[]{
"connection"
, "transfer-encoding"
, "content-length" // POST kann zu Status 500 führen, wenn die content-length kopiert wird
, "via"
, "x-forwarded-for"
, "x-forwarded-host"
, "x-forwarded-server"
}));
private void copyRequestHeaders(HttpServletRequest customerRequest, HttpRequestBase internRequest) throws
HttpException
{
Enumeration<String> headerNames = customerRequest.getHeaderNames();
String connectionHeader = customerRequest.getHeader("connection");
while (headerNames.hasMoreElements())
{
String headerName = headerNames.nextElement();
boolean copyAllowed = !forbiddenCopyHeaders.contains(headerName.toLowerCase()) &&
!StringUtils.containsIgnoreCase(connectionHeader, headerName);
if (copyAllowed)
{
Enumeration<String> values = customerRequest.getHeaders(headerName);
while (values.hasMoreElements())
{
internRequest.addHeader(headerName, values.nextElement());
}
}
}
setProxySpecificRequestHeaders(customerRequest, internRequest);
}
private void setProxySpecificRequestHeaders(HttpServletRequest customerRequest,
HttpRequestBase internRequest) throws HttpException
{
String serverHostName = "doorman";
try
{
serverHostName = InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e)
{
logger.error("Couldn't get the hostname needed for headers x-forwarded-server and Via", e);
}
String originalVia = customerRequest.getHeader("via");
StringBuilder via = new StringBuilder("");
if (originalVia != null)
{
if (originalVia.contains(serverHostName))
{
logger.error("This proxy has already handled the Request, will abort.");
throw new HttpException("Request has a cyclic dependency on this proxy.");
}
else
{
via.append(originalVia).append(", ");
}
}
via.append(customerRequest.getProtocol()).append(" ").append(serverHostName);
internRequest.addHeader("via", via.toString());
internRequest.addHeader("x-forwarded-for", customerRequest.getRemoteAddr());
internRequest.addHeader("x-forwarded-host", customerRequest.getServerName());
internRequest.addHeader("x-forwarded-server", serverHostName);
internRequest.addHeader("accept-encoding", "");
}
Using HttpURLConnection and altering the header to include a property from the original request, I was able to get a BufferedReader from the HTTP request:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// Step 1
String serverName = request.getLocalName();
String contextPath = request.getContextPath();
URL url = new URL("https://" + serverName + contextPath + "/baz");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Key Header", request.getHeader("Key Header"));
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
// Step 2
... // Do something with the data from the reader
// Step 3
... // Write the data back using the response
}
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();
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?
I have several servlets, which
take JSON-encoded requests as inputs,
process them and
return responses to the client as JSON-encoded objects.
Up to now I used Android as client (sample Android code see below).
Now I want to write a plain old Java program, which would send requests and receive the responses (do the same as the Android code). For this purpose I wrote a Java test (code see below, section Java code) and ran it.
At the client side I get this error:
21:43:38.930 [main] ERROR r.a.c.t.TestAcceptanceProcedure1 -
java.io.IOException: Server returned HTTP response code: 405 for URL: http://myserver/myapp/rest/GetUserIdService
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441) ~[na:1.6.0_23]
at testclient.TestAcceptanceProcedure1.test(TestAcceptanceProcedure1.java:47) ~[test-classes/:na]
In the server log, I see this message:
WARNING: No operation matching request path "/myapp/rest/GetUserIdService" is found, Relative Path: /, HTTP Method: GET, ContentType: */*, Accept: text/html,image/gif,image/jpeg,*/*,*/*;q=.2,. Please enable FINE/TRACE log level for more details
Question: How should I change my Java test to fix this error?
Note that the server is up and running (when I execute the Android code, it works).
Android code:
Sending the request and receiving the response:
final GetSimulationStatusRequest request = new GetSimulationStatusRequest();
final String json = Utils.convertToJson(request, getClass());
final String serverUrl = Utils.getServerUrl(this, "GetSimulationStatusService");
final IGetSimulationStatusAsyncTask getSimulationStatusTask =
asyncTaskFactory.createGetSimulationStatusAsyncTask(getWebServiceHelper());
Utils.setRequestAndServerUrl(json, serverUrl, getSimulationStatusTask);
final GetSimulationStatusResponse simulationStatusReponse =
getSimulationStatusTask.get();
Utils.convertToJson:
public static String convertToJson(final Object aRequest, Class<? extends Activity> aClass) {
final ObjectMapper mapper = new ObjectMapper();
String json = null;
try {
json = mapper.writeValueAsString(aRequest);
} catch (final JsonProcessingException exception) {
Log.e(aClass.getSimpleName(), exception.getLocalizedMessage(),
exception);
}
return json;
}
Utils.setRequestAndServerUrl:
public static void setRequestAndServerUrl(final String aJson,
final String aServerUrl, final IAsyncTask aTask) {
aTask.addNameValuePair("request", aJson);
aTask.sendRequest(new String[] { aServerUrl });
}
GetSimulationStatusAsyncTask:
public class GetSimulationStatusAsyncTask
extends AsyncTask<String, String, GetSimulationStatusResponse>
implements IGetSimulationStatusAsyncTask {
private static final String TAG = GetSimulationStatusAsyncTask.class.getSimpleName();
private IWebServiceTaskHelper helper;
private ICcpResponseParser<GetSimulationStatusResponse> responseParser =
new CcpResponseParser<GetSimulationStatusResponse>();
public GetSimulationStatusAsyncTask(final IWebServiceTaskHelper aHelper) {
helper = aHelper;
}
#Override
public void addNameValuePair(final String aName, final String aValue) {
helper.addNameValuePair(aName, aValue);
}
#Override
protected GetSimulationStatusResponse doInBackground(String... aArgs) {
return (GetSimulationStatusResponse)Utils.processResponse(this.helper, TAG, responseParser,
GetSimulationStatusResponse.class, aArgs);
}
#Override
public void sendRequest(final String[] aArgs) {
execute(aArgs);
}
}
Java code:
#Test
public void test() throws JsonProcessingException, MalformedURLException {
final GetUserIdRequest request = new GetUserIdRequest();
request.setDeviceId("PC1");
final String requestAsString = convertToJson(request);
final String serverUrl = getServerUrl("GetUserIdService");
final URL url = new URL(serverUrl);
HttpURLConnection connection = null;
InputStream inputStream = null;
try {
connection = (HttpURLConnection) url.openConnection();
connection.addRequestProperty("request", requestAsString);
connection.connect();
inputStream = connection.getInputStream();
final String responseAsString = IOUtils.toString(inputStream);
LOGGER.debug("responseAsString: " + responseAsString);
} catch (final IOException exception) {
LOGGER.error("", exception);
}
finally
{
IOUtils.closeQuietly(inputStream);
}
}
private String convertToJson(final GetUserIdRequest aRequest) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(aRequest);
}
private String getServerUrl(final String aServiceName)
{
return "http://myserver.com/myapp/rest/" + aServiceName;
}
Update 1 (09.10.2013 12:23 MSK):
#Path("/GetSimulationStatusService")
public class GetSimulationStatusService extends BaseCcpService {
private GetSimulationStatusRequestParser requestParser =
new GetSimulationStatusRequestParser();
#POST
#Produces("text/plain")
public String getSimulationStatus(#FormParam("request") final String aRequestJson)
throws JsonProcessingException
{
final GetSimulationStatusRequest request = requestParser.convert(aRequestJson);
final GetSimulationStatusResponse response = new GetSimulationStatusResponse();
response.setRequestId(request.getId());
response.setCycle(getPersistence().getCurrentCycle(request.getUserId()));
response.setLabourForce(getPersistence().getLabourForceSimulationParameter(
request.getUserId()));
return getObjectMapper().writeValueAsString(response);
}
}
Update 2 (09.10.2013 20:48 MSK): When I change the code like shown below, I get 500 HTTP response. At the server side, the aRequest argument of the method GetUserIdService.getUserId is null.
connection = (HttpURLConnection) url.openConnection();
connection.addRequestProperty("request", requestAsString);
connection.setRequestMethod("POST"); // Added this line
connection.connect();
Update 3 (09.10.2013 23:15): This one works:
#Test
public void test() throws JsonProcessingException, MalformedURLException
{
final GetUserIdRequest request = new GetUserIdRequest();
request.setDeviceId("PC1");
final String requestAsString = convertToJson(request);
final String serverUrl = getServerUrl("GetUserIdService");
final URL url = new URL(serverUrl);
HttpURLConnection connection = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.connect();
outputStream = connection.getOutputStream();
IOUtils.write("request=" + requestAsString, outputStream);
inputStream = connection.getInputStream();
final String responseAsString = IOUtils.toString(inputStream);
LOGGER.debug("responseAsString: " + responseAsString);
} catch (final IOException exception) {
LOGGER.error("", exception);
}
finally
{
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
}
The 405 HTTP error code means that given method (GET) is not supported by the endpoint. Probably instead of GET request you want to send POST. I don't know what kind of request is sent by Android client.
Do you have endpoint specification/documentation?
Here you'll find information how to invoke POST using plain Java API. If you can use external libraries in your test then it can be achieved a lot easier using RESTEasy.