Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to configure Wildfly to serve static content (like images)?

I have a JavaEE application running on Wildfly 8.0.0 Final.

The application uses a lot of images and I don't want to store them in the database, so they are written to the hard disk.

How can I configure Wildfly/Undertow in order to serve these files (/var/images) on a certain URL, for example http://localhost:8080/myapplication/imagesFromDisk?

like image 982
Christian Götz Avatar asked Mar 27 '14 09:03

Christian Götz


People also ask

How is static content served?

Static content is any content that can be delivered to an end user without having to be generated, modified, or processed. The server delivers the same file to each user, making static content one of the simplest and most efficient content types to transmit over the Internet.

How do you use WildFly?

Use the script <WildFly directory>\bin\standalone. bat to start the WildFly server and check the installation. After startup, you should be able to access the web server at http://localhost:8080. Open the link Administration Console and follow the instructions to add a new management user.


2 Answers

Add another file handler and another location to the undertow subsystem in standalone.xml:

<server name="default-server">     <http-listener name="default" socket-binding="http"/>     <host name="default-host" alias="localhost">         <location name="/" handler="welcome-content"/>         <location name="/img" handler="images"/>     </host> </server> <handlers>     <file name="welcome-content" path="${jboss.home.dir}/welcome-content" directory-listing="true"/>     <file name="images" path="/var/images" directory-listing="true"/> </handlers> 
like image 152
Harald Wellmann Avatar answered Oct 13 '22 09:10

Harald Wellmann


In case you don't want to, or can't, configure the Undertow subsystem in standalone.xml, then you need to make it work within the .war. For that, you may use a servlet to serve the files. There are few implementations around, the most famous (or highest on Google) is by BalusC. That one is licensed under LGPL, though. So I took the DefaultServlet and made it more easily extensible, so you can feed it the right Undertow Resource, e.g. from a file system.

Theoretically, some servlet in Undertow should be able to serve from a filesystem, too, but the DefaultServlet code seems to only count with resources within a deployment.

Base class:

 /*  * JBoss, Home of Professional Open Source.  * Copyright 2014 Red Hat, Inc., and individual contributors  * as indicated by the @author tags.  *  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  *  Unless required by applicable law or agreed to in writing, software  *  distributed under the License is distributed on an "AS IS" BASIS,  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *  See the License for the specific language governing permissions and  *  limitations under the License.  */ package io.undertow.servlet.handlers;  import io.undertow.io.IoCallback; import io.undertow.io.Sender; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.DirectoryUtils; import io.undertow.server.handlers.resource.RangeAwareResource; import io.undertow.server.handlers.resource.Resource; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.servlet.api.DefaultServletConfig; import io.undertow.servlet.api.Deployment; import io.undertow.servlet.spec.ServletContextImpl; import io.undertow.util.ByteRange; import io.undertow.util.CanonicalPathUtils; import io.undertow.util.DateUtils; import io.undertow.util.ETag; import io.undertow.util.ETagUtils; import io.undertow.util.Headers; import io.undertow.util.Methods; import io.undertow.util.StatusCodes;  import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Set;  /**  * Abstract default servlet implementation for serving static content.  * This is both a handler and a servlet. If no filters  * match the current path then the content will be served up asynchronously using the  * {@link io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange)} method,  * otherwise the request is handled as a normal servlet request.  * <p>  * By default we only allow a restricted set of extensions.  * <p>  *  * @author Stuart Douglas  * @author Ondrej Zizka, zizka at seznam.cz  */ public abstract class DefaultServlet extends HttpServlet {      public static final String DIRECTORY_LISTING = "directory-listing";     public static final String DEFAULT_ALLOWED = "default-allowed";     public static final String ALLOWED_EXTENSIONS = "allowed-extensions";     public static final String DISALLOWED_EXTENSIONS = "disallowed-extensions";     public static final String RESOLVE_AGAINST_CONTEXT_ROOT = "resolve-against-context-root";      private static final Set<String> DEFAULT_ALLOWED_EXTENSIONS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(         "js", "css", "png", "jpg", "gif", "html", "htm", "txt", "pdf", "jpeg", "xml",         "svg", "ttf", "java")));      private Deployment deployment;     private ResourceManager resourceManager;      private final Settings settings = new Settings();      public Settings getSettings() {     return settings;     }        @Override     public void init(ServletConfig config) throws ServletException {     super.init(config);      ServletContextImpl sc = (ServletContextImpl) config.getServletContext();     this.deployment = sc.getDeployment();      // Settings from the servlet init parameters.     if (config.getInitParameter(DEFAULT_ALLOWED) != null) {         settings.defaultAllowed = Boolean.parseBoolean(config.getInitParameter(DEFAULT_ALLOWED));     }     if (config.getInitParameter(ALLOWED_EXTENSIONS) != null) {         String extensions = config.getInitParameter(ALLOWED_EXTENSIONS);         settings.allowed = new HashSet<>(Arrays.asList(extensions.split(",")));     }     if (config.getInitParameter(DISALLOWED_EXTENSIONS) != null) {         String extensions = config.getInitParameter(DISALLOWED_EXTENSIONS);         settings.disallowed = new HashSet<>(Arrays.asList(extensions.split(",")));     }     if (config.getInitParameter(RESOLVE_AGAINST_CONTEXT_ROOT) != null) {         settings.resolveAgainstContextRoot = Boolean.parseBoolean(config.getInitParameter(RESOLVE_AGAINST_CONTEXT_ROOT));     }      String listings = config.getInitParameter(DIRECTORY_LISTING);     if (Boolean.valueOf(listings)) {         settings.directoryListingEnabled = true;     }      // Settings from the deployment configuration.     DefaultServletConfig defaultServletConfig = getDeployment().getDeploymentInfo().getDefaultServletConfig();     if (defaultServletConfig != null) {         getSettings().setDefaultAllowed(defaultServletConfig.isDefaultAllowed());         getSettings().setAllowed(new HashSet<>());         if (defaultServletConfig.getAllowed() != null) {             getSettings().getAllowed().addAll(defaultServletConfig.getAllowed());         }         getSettings().setDisallowed(new HashSet<>());         if (defaultServletConfig.getDisallowed() != null) {             getSettings().getDisallowed().addAll(defaultServletConfig.getDisallowed());         }     }      }       @Override     protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {     String path = getPath(req);     if (!isAllowed(path, req.getDispatcherType())) {         resp.sendError(StatusCodes.NOT_FOUND);         return;     }     if(File.separatorChar != '/') {         //if the separator char is not / we want to replace it with a / and canonicalise         path = CanonicalPathUtils.canonicalize(path.replace(File.separatorChar, '/'));     }      final Resource resource;     //we want to disallow windows characters in the path     if(File.separatorChar == '/' || !path.contains(File.separator)) {         resource = resourceManager.getResource(path);     } else {         resource = null;     }      if (resource == null) {         if (req.getDispatcherType() == DispatcherType.INCLUDE) {             //servlet 9.3             throw new FileNotFoundException(path);         } else {             resp.sendError(StatusCodes.NOT_FOUND);         }         return;     }      // Directory     if (resource.isDirectory()) {         if (!settings.directoryListingEnabled)             resp.sendError(StatusCodes.FORBIDDEN);          //  /servlet-name/some/dir/?css or ?js         if ("css".equals(req.getQueryString())) {             resp.setContentType("text/css");             resp.getWriter().write(DirectoryUtils.Blobs.FILE_CSS);             return;         } else if ("js".equals(req.getQueryString())) {             resp.setContentType("application/javascript");             resp.getWriter().write(DirectoryUtils.Blobs.FILE_JS);             return;         }          StringBuilder output = DirectoryUtils.renderDirectoryListing(req.getRequestURI(), resource);         resp.getWriter().write(output.toString());     }      // File     else {         if(path.endsWith("/")) {             //UNDERTOW-432             resp.sendError(StatusCodes.NOT_FOUND);             return;         }         serveFileBlocking(req, resp, resource);     }     }      @Override     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {     /*      * Where a servlet has received a POST request we still require the capability to include static content.      */     switch (req.getDispatcherType()) {         case INCLUDE:         case FORWARD:         case ERROR:             doGet(req, resp);             break;         default:             super.doPost(req, resp);     }     }      @Override     protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {     switch (req.getDispatcherType()) {         case INCLUDE:         case FORWARD:         case ERROR:             doGet(req, resp);             break;         default:             super.doPut(req, resp);     }     }      @Override     protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {     switch (req.getDispatcherType()) {         case INCLUDE:         case FORWARD:         case ERROR:             doGet(req, resp);             break;         default:             super.doDelete(req, resp);     }     }      @Override     protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {     switch (req.getDispatcherType()) {         case INCLUDE:         case FORWARD:         case ERROR:             doGet(req, resp);             break;         default:             super.doOptions(req, resp);     }     }      @Override     protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {     switch (req.getDispatcherType()) {         case INCLUDE:         case FORWARD:         case ERROR:             doGet(req, resp);             break;         default:             super.doTrace(req, resp);     }     }      protected void serveFileBlocking(final HttpServletRequest req, final HttpServletResponse resp, final Resource resource) throws IOException {     final ETag etag = resource.getETag();     final Date lastModified = resource.getLastModified();     if(req.getDispatcherType() != DispatcherType.INCLUDE) {         if (!ETagUtils.handleIfMatch(req.getHeader(Headers.IF_MATCH_STRING), etag, false) ||                 !DateUtils.handleIfUnmodifiedSince(req.getHeader(Headers.IF_UNMODIFIED_SINCE_STRING), lastModified)) {             resp.setStatus(StatusCodes.PRECONDITION_FAILED);             return;         }         if (!ETagUtils.handleIfNoneMatch(req.getHeader(Headers.IF_NONE_MATCH_STRING), etag, true) ||                 !DateUtils.handleIfModifiedSince(req.getHeader(Headers.IF_MODIFIED_SINCE_STRING), lastModified)) {             resp.setStatus(StatusCodes.NOT_MODIFIED);             return;         }     }      //we are going to proceed. Set the appropriate headers     if(resp.getContentType() == null) {         if(!resource.isDirectory()) {             final String contentType = deployment.getServletContext().getMimeType(resource.getName());             if (contentType != null) {                 resp.setContentType(contentType);             } else {                 resp.setContentType("application/octet-stream");             }         }     }     if (lastModified != null) {         resp.setHeader(Headers.LAST_MODIFIED_STRING, resource.getLastModifiedString());     }     if (etag != null) {         resp.setHeader(Headers.ETAG_STRING, etag.toString());     }     ByteRange range = null;     long start = -1, end = -1;     try {         //only set the content length if we are using a stream         //if we are using a writer who knows what the length will end up being         //todo: if someone installs a filter this can cause problems         //not sure how best to deal with this         //we also can't deal with range requests if a writer is in use         Long contentLength = resource.getContentLength();         if (contentLength != null) {             resp.getOutputStream();             if(contentLength > Integer.MAX_VALUE) {                 resp.setContentLengthLong(contentLength);             } else {                 resp.setContentLength(contentLength.intValue());             }             if(resource instanceof RangeAwareResource && ((RangeAwareResource)resource).isRangeSupported()) {                 //TODO: figure out what to do with the content encoded resource manager                 range = ByteRange.parse(req.getHeader(Headers.RANGE_STRING));                 if(range != null && range.getRanges() == 1) {                     start = range.getStart(0);                     end = range.getEnd(0);                     if(start == -1 ) {                         //suffix range                         long toWrite = end;                         if(toWrite >= 0) {                             if(toWrite > Integer.MAX_VALUE) {                                 resp.setContentLengthLong(toWrite);                             } else {                                 resp.setContentLength((int)toWrite);                             }                         } else {                             //ignore the range request                             range = null;                         }                         start = contentLength - end;                         end = contentLength;                     } else if(end == -1) {                         //prefix range                         long toWrite = contentLength - start;                         if(toWrite >= 0) {                             if(toWrite > Integer.MAX_VALUE) {                                 resp.setContentLengthLong(toWrite);                             } else {                                 resp.setContentLength((int)toWrite);                             }                         } else {                             //ignore the range request                             range = null;                         }                         end = contentLength;                     } else {                         long toWrite = end - start + 1;                         if(toWrite > Integer.MAX_VALUE) {                             resp.setContentLengthLong(toWrite);                         } else {                             resp.setContentLength((int)toWrite);                         }                     }                     if(range != null) {                         resp.setStatus(StatusCodes.PARTIAL_CONTENT);                         resp.setHeader(Headers.CONTENT_RANGE_STRING, range.getStart(0) + "-" + range.getEnd(0) + "/" + contentLength);                     }                 }             }         }     } catch (IllegalStateException e) {      }     final boolean include = req.getDispatcherType() == DispatcherType.INCLUDE;     if (!req.getMethod().equals(Methods.HEAD_STRING)) {         HttpServerExchange exchange = SecurityActions.requireCurrentServletRequestContext().getOriginalRequest().getExchange();         if(range == null) {             resource.serve(exchange.getResponseSender(), exchange, completionCallback(include));         } else {             ((RangeAwareResource)resource).serveRange(exchange.getResponseSender(), exchange, start, end, completionCallback(include));         }     }     }      private IoCallback completionCallback(final boolean include) {     return new IoCallback() {          @Override         public void onComplete(final HttpServerExchange exchange, final Sender sender) {             if (!include) {                 sender.close();             }         }          @Override         public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) {             //not much we can do here, the connection is broken             sender.close();         }     };     }      /**      * Returns the canonicalized URL path.      */     private String getPath(final HttpServletRequest request) {     String servletPath;     String pathInfo;      if (request.getDispatcherType() == DispatcherType.INCLUDE && request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) {         pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);         servletPath = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);     } else {         pathInfo = request.getPathInfo();         servletPath = request.getServletPath();     }     String result = pathInfo;     if (result == null) {         result = servletPath;     } else if(settings.resolveAgainstContextRoot) {         result = servletPath + CanonicalPathUtils.canonicalize(pathInfo);     } else {         result = CanonicalPathUtils.canonicalize(result);     }     if ((result == null) || (result.equals(""))) {         result = "/";     }     return result;      }      /**      * @return true if the resource at the given path is allowed to be sent to the client.      */     protected boolean isAllowed(String path, DispatcherType dispatcherType) {     int pos = path.lastIndexOf('/');     final String lastSegment;     if (pos == -1) {         lastSegment = path;     } else {         lastSegment = path.substring(pos + 1);     }     if (lastSegment.isEmpty()) {         return true;     }     int ext = lastSegment.lastIndexOf('.');     if (ext == -1) {         //no extension         return true;     }     final String extension = lastSegment.substring(ext + 1, lastSegment.length());     if (settings.defaultAllowed) {         return !settings.disallowed.contains(extension);     } else {         return settings.allowed.contains(extension);     }     }      public boolean isDirectoryListingEnabled() {     return settings.directoryListingEnabled;     }       public void setDirectoryListingEnabled(boolean directoryListingEnabled) {     settings.directoryListingEnabled = directoryListingEnabled;     }      public ResourceManager getResourceManager() {     return resourceManager;     }       public void setResourceManager(ResourceManager resourceManager) {     this.resourceManager = resourceManager;     }       public Deployment getDeployment() {     return deployment;     }         protected static class Settings     {     private boolean directoryListingEnabled = false;     private boolean defaultAllowed = true;     private Set<String> allowed = DEFAULT_ALLOWED_EXTENSIONS;     private Set<String> disallowed = Collections.emptySet();     private boolean resolveAgainstContextRoot;       public boolean isDirectoryListingEnabled() {         return directoryListingEnabled;     }      public void setDirectoryListingEnabled(boolean directoryListingEnabled) {         this.directoryListingEnabled = directoryListingEnabled;     }      public boolean isDefaultAllowed() {         return defaultAllowed;     }      public void setDefaultAllowed(boolean defaultAllowed) {         this.defaultAllowed = defaultAllowed;     }      public Set<String> getAllowed() {         return allowed;     }      public void setAllowed(Set<String> allowed) {         this.allowed = allowed;     }      public Set<String> getDisallowed() {         return disallowed;     }      public void setDisallowed(Set<String> disallowed) {         this.disallowed = disallowed;     }      public boolean isResolveAgainstContextRoot() {         return resolveAgainstContextRoot;     }      public void setResolveAgainstContextRoot(boolean resolveAgainstContextRoot) {         this.resolveAgainstContextRoot = resolveAgainstContextRoot;     }     } } 

The file serving servlet (the one you will use in web.xml):

/*  * JBoss, Home of Professional Open Source.  * Copyright 2014 Red Hat, Inc., and individual contributors  * as indicated by the @author tags.  *  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  *  Unless required by applicable law or agreed to in writing, software  *  distributed under the License is distributed on an "AS IS" BASIS,  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *  See the License for the specific language governing permissions and  *  limitations under the License.  */ package org.jboss.windup.web.fileservlet;  import io.undertow.Undertow; import java.util.logging.Logger; import io.undertow.server.handlers.resource.FileResourceManager; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import java.io.File; import org.jboss.windup.web.addons.websupport.WebPathUtil;  /**  * Default servlet responsible for serving up files from a directory.  * This is both a handler and a servlet. If no filters  * match the current path, then the resources will be served up asynchronously using the  * {@link io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange)} method,  * otherwise the request is handled as a normal servlet request.  *  * <p>  * By default we only allow a restricted set of extensions.  * <p>   * @author Ondrej Zizka, zizka at seznam.cz  * @see Undertow's DefaultServlet  */ public class FileDefaultServlet extends DefaultServlet {     public static final String BASE_PATH = "base-path";      /**      * Which directory should this servlet serve files from.      */     private String basePath;       @Override     public void init(ServletConfig config) throws ServletException {     super.init(config);      initBasePath();     this.setResourceManager(new FileResourceManager(new File(basePath), 8*1024));     }       private void initBasePath() throws ServletException     {     // Get base path (path to get all resources from) as init parameter.     this.basePath = getInitParameter(BASE_PATH);     this.basePath = WebPathUtil.expandVariables(this.basePath);      // Validate base path.     if (this.basePath == null) {         throw new ServletException("FileServlet init param 'basePath' is required.");     } else {         File path = new File(this.basePath);         if (!path.exists()) {             throw new ServletException("FileServlet init param 'basePath' value '"                     + this.basePath + "' does actually not exist in file system.");         } else if (!path.isDirectory()) {             throw new ServletException("FileServlet init param 'basePath' value '"                     + this.basePath + "' is actually not a directory in file system.");         } else if (!path.canRead()) {             throw new ServletException("FileServlet init param 'basePath' value '"                     + this.basePath + "' is actually not readable in file system.");         }     }     }  } 

And here's a servlet which works like the previous DefaultServlet - that is, serving from the deployment.

/*  * JBoss, Home of Professional Open Source.  * Copyright 2014 Red Hat, Inc., and individual contributors  * as indicated by the @author tags.  *  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *     http://www.apache.org/licenses/LICENSE-2.0  *  *  Unless required by applicable law or agreed to in writing, software  *  distributed under the License is distributed on an "AS IS" BASIS,  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *  See the License for the specific language governing permissions and  *  limitations under the License.  */ package io.undertow.servlet.handlers;  import javax.servlet.DispatcherType; import javax.servlet.ServletConfig; import javax.servlet.ServletException;  /**  * Default servlet responsible for serving up resources. This is both a handler and a servlet. If no filters  * match the current path then the resources will be served up asynchronously using the  * {@link io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange)} method,  * otherwise the request is handled as a normal servlet request.  * <p>  * By default we only allow a restricted set of extensions.  * <p>  * todo: this thing needs a lot more work. In particular:  * - caching for blocking requests  * - correct mime type  * - range/last-modified and other headers to be handled properly  * - head requests  * - and probably heaps of other things  *  * @author Ondrej Zizka, zizka at seznam.cz  */ public class ResourceDefaultServlet extends DefaultServlet {      @Override     public void init(ServletConfig config) throws ServletException {     super.init(config);      this.setResourceManager(getDeployment().getDeploymentInfo().getResourceManager());     }       @Override     protected boolean isAllowed(String path, DispatcherType dispatcherType) {      if (!path.isEmpty()) {         if(dispatcherType == DispatcherType.REQUEST) {             //WFLY-3543 allow the dispatcher to access stuff in web-inf and meta inf             if (path.startsWith("/META-INF") ||                     path.startsWith("META-INF") ||                     path.startsWith("/WEB-INF") ||                     path.startsWith("WEB-INF")) {                 return false;             }         }     }      return super.isAllowed(path, dispatcherType);     }  } 
like image 42
Ondra Žižka Avatar answered Oct 13 '22 08:10

Ondra Žižka