Java 如何使用suns simple httpserver提供静态内容

Java 如何使用suns simple httpserver提供静态内容,java,jersey,httpserver,Java,Jersey,Httpserver,我正在使用jersey的HttpServerFactory创建一个简单的嵌入式HttpServer,它承载了几个rest服务。我们只需要一些小巧、快速、轻便的东西。我需要在同一个服务器实例中托管一个小的静态html页面。有没有简单的方法向服务器添加静态处理程序?是否有预定义的处理程序可以使用?这似乎是一项非常常见的任务,如果它已经存在,我不愿意为它重新编写代码 server = HttpServerFactory.create(url); server.setExecutor(Executors

我正在使用jersey的
HttpServerFactory
创建一个简单的嵌入式
HttpServer
,它承载了几个rest服务。我们只需要一些小巧、快速、轻便的东西。我需要在同一个服务器实例中托管一个小的静态html页面。有没有简单的方法向服务器添加静态处理程序?是否有预定义的处理程序可以使用?这似乎是一项非常常见的任务,如果它已经存在,我不愿意为它重新编写代码

server = HttpServerFactory.create(url);
server.setExecutor(Executors.newCachedThreadPool());
server.createContext("/staticcontent", new HttpHandler() {
    @Override
    public void handle(HttpExchange arg0) throws IOException {
        //What goes here?
    }
});
server.start();

这将达到目的,尽管它允许任何人通过请求..// 您可以将./wwwroot更改为任何有效的java文件路径

static class MyHandler implements HttpHandler {
    public void handle(HttpExchange t) throws IOException {
        String root = "./wwwroot";
        URI uri = t.getRequestURI();
        System.out.println("looking for: "+ root + uri.getPath());
        String path = uri.getPath();
        File file = new File(root + path).getCanonicalFile();

        if (!file.isFile()) {
          // Object does not exist or is not a file: reject with 404 error.
          String response = "404 (Not Found)\n";
          t.sendResponseHeaders(404, response.length());
          OutputStream os = t.getResponseBody();
          os.write(response.getBytes());
          os.close();
        } else {
          // Object exists and is a file: accept with response code 200.
          String mime = "text/html";
          if(path.substring(path.length()-3).equals(".js")) mime = "application/javascript";
          if(path.substring(path.length()-3).equals("css")) mime = "text/css";            

          Headers h = t.getResponseHeaders();
          h.set("Content-Type", mime);
          t.sendResponseHeaders(200, 0);              

          OutputStream os = t.getResponseBody();
          FileInputStream fs = new FileInputStream(file);
          final byte[] buffer = new byte[0x10000];
          int count = 0;
          while ((count = fs.read(buffer)) >= 0) {
            os.write(buffer,0,count);
          }
          fs.close();
          os.close();
        }  
    }
}

这是一个安全的版本。您可能需要添加几个MIME类型,具体取决于哪些类型是常见的(如果需要,也可以使用另一种方法)

package de.phihag.miniticker;
导入java.io.File;
导入java.io.FileInputStream;
导入java.io.FileNotFoundException;
导入java.io.IOException;
导入java.io.InputStream;
导入java.io.OutputStream;
导入java.util.HashMap;
导入java.util.Map;
导入com.sun.net.httpserver.HttpExchange;
导入com.sun.net.httpserver.HttpHandler;
导入com.sun.net.httpserver.httpserver;
公共类StaticFileHandler实现HttpHandler{
私有静态最终映射MIME_Map=new HashMap();
静止的{
MIME_MAP.put(“appcache”、“文本/缓存清单”);
MIME_MAP.put(“css”、“text/css”);
MIME_MAP.put(“gif”、“image/gif”);
MIME_MAP.put(“html”、“text/html”);
MIME_MAP.put(“js”,“application/javascript”);
MIME_MAP.put(“json”,“application/json”);
MIME_MAP.put(“jpg”、“image/jpeg”);
MIME_MAP.put(“jpeg”、“图像/jpeg”);
MIME_MAP.put(“mp4”、“视频/mp4”);
MIME_MAP.put(“pdf”、“application/pdf”);
MIME_MAP.put(“png”,“image/png”);
MIME_MAP.put(“svg”、“image/svg+xml”);
MIME_MAP.put(“xlsm”,“application/vnd.openxmlformats of icedocument.spreadsheetml.sheet”);
MIME_MAP.put(“xml”,“application/xml”);
MIME_MAP.put(“zip”、“application/zip”);
MIME_MAP.put(“md”、“text/plain”);
MIME_MAP.put(“txt”,“text/plain”);
MIME_MAP.put(“php”,“text/plain”);
};
私有字符串文件系统根;
私有字符串前缀;
私有字符串目录索引;
/**
*@param url前缀所有URL的前缀。
*这是createContext的第一个参数。必须以斜杠开始和结束。
*@param filesystemRoot文件系统中的根目录。
*只有此目录下的文件才会提供给客户端。
*例如“/staticfiles”。
*@param directoryIndex文件,用于在请求目录时显示,例如“index.html”。
*/
公共StaticFileHandler(字符串URL前缀、字符串filesystemRoot、字符串目录索引){
如果(!urlPrefix.startsWith(“/”){
抛出新的RuntimeException(“pathPrefix不以斜杠开头”);
}
如果(!urlPrefix.endsWith(“/”)){
抛出新的RuntimeException(“pathPrefix不以斜杠结尾”);
}
this.urlPrefix=urlPrefix;
断言filesystemRoot.endsWith(“/”);
试一试{
this.filesystemRoot=新文件(filesystemRoot).getCanonicalPath();
}捕获(IOE异常){
抛出新的运行时异常(e);
}
this.directoryIndex=directoryIndex;
}
/**
*创建并注册一个新的静态文件处理程序。
*@param hs将在其中注册文件处理程序的HTTP服务器。
*@param path URL中以所有请求为前缀的路径,例如“/static/”
*@param filesystemRoot文件系统位置。
*例如“/var/www/mystaticfiles/”。
*将从文件系统文件“/var/www/mystaticfiles/x/y.html”提供对“/static/x/y.html”的请求
*@param directoryIndex文件,用于在请求目录时显示,例如“index.html”。
*/
公共静态void创建(HttpServer hs、字符串路径、字符串文件系统根、字符串目录索引){
StaticFileHandler sfh=新的StaticFileHandler(路径、filesystemRoot、目录索引);
hs.createContext(路径,sfh);
}
公共无效句柄(HttpExchange he)引发IOException{
String方法=he.getRequestMethod();
if(!((“HEAD.equals(方法)| |“GET.equals(方法))){
sendError(他,501,“不支持的HTTP方法”);
返回;
}
字符串wholeUrlPath=he.getRequestURI().getPath();
if(wholeUrlPath.endsWith(“/”)){
wholeUrlPath+=目录索引;
}
如果(!wholeUrlPath.startsWith(urlPrefix)){
抛出新的RuntimeException(“路径不在前缀中-路由错误?”);
}
字符串urlPath=wholeUrlPath.substring(urlPrefix.length());
文件f=新文件(filesystemRoot,urlPath);
文件规范文件;
试一试{
canonicalFile=f.getCanonicalFile();
}捕获(IOE异常){
//这可能更温和(即不是攻击,只是403),
//但我们不希望攻击者能够辨别出区别。
报告路径遍历(he);
返回;
}
字符串canonicalPath=canonicalFile.getPath();
如果(!canonicalPath.startsWith(filesystemRoot)){
报告路径遍历(he);
返回;
}
文件输入流fis;
试一试{
fis=新文件输入流(canonicalFile);
}catch(filenotfounde异常){
//该文件也可能被禁止使用,而不是丢失,但我们通过这种方式泄漏的信息越来越少
sendError(他,404,“未找到文件”);
返回;
}
字符串mimeType=lookupMime(urlPath);
he.getResponseHeaders().set(“内容类型”,mimeType);
if(“GET”.equals(方法)){
他
package de.phihag.miniticker;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class StaticFileHandler implements HttpHandler {
    private static final Map<String,String> MIME_MAP = new HashMap<>();
    static {
        MIME_MAP.put("appcache", "text/cache-manifest");
        MIME_MAP.put("css", "text/css");
        MIME_MAP.put("gif", "image/gif");
        MIME_MAP.put("html", "text/html");
        MIME_MAP.put("js", "application/javascript");
        MIME_MAP.put("json", "application/json");
        MIME_MAP.put("jpg", "image/jpeg");
        MIME_MAP.put("jpeg", "image/jpeg");
        MIME_MAP.put("mp4", "video/mp4");
        MIME_MAP.put("pdf", "application/pdf");
        MIME_MAP.put("png", "image/png");
        MIME_MAP.put("svg", "image/svg+xml");
        MIME_MAP.put("xlsm", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        MIME_MAP.put("xml", "application/xml");
        MIME_MAP.put("zip", "application/zip");
        MIME_MAP.put("md", "text/plain");
        MIME_MAP.put("txt", "text/plain");
        MIME_MAP.put("php", "text/plain");
    };

    private String filesystemRoot;
    private String urlPrefix;
    private String directoryIndex;

    /**
     * @param urlPrefix The prefix of all URLs.
     *                   This is the first argument to createContext. Must start and end in a slash.
     * @param filesystemRoot The root directory in the filesystem.
     *                       Only files under this directory will be served to the client.
     *                       For instance "./staticfiles".
     * @param directoryIndex File to show when a directory is requested, e.g. "index.html".
     */
    public StaticFileHandler(String urlPrefix, String filesystemRoot, String directoryIndex) {
        if (!urlPrefix.startsWith("/")) {
            throw new RuntimeException("pathPrefix does not start with a slash");
        }
        if (!urlPrefix.endsWith("/")) {
            throw new RuntimeException("pathPrefix does not end with a slash");
        }
        this.urlPrefix = urlPrefix;

        assert filesystemRoot.endsWith("/");
        try {
            this.filesystemRoot = new File(filesystemRoot).getCanonicalPath();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        this.directoryIndex = directoryIndex;
    }

    /**
     * Create and register a new static file handler.
     * @param hs The HTTP server where the file handler will be registered.
     * @param path The path in the URL prefixed to all requests, such as "/static/"
     * @param filesystemRoot The filesystem location.
     *                       For instance "/var/www/mystaticfiles/".
     *                       A request to "/static/x/y.html" will be served from the filesystem file "/var/www/mystaticfiles/x/y.html"
     * @param directoryIndex File to show when a directory is requested, e.g. "index.html".
     */
    public static void create(HttpServer hs, String path, String filesystemRoot, String directoryIndex) {
        StaticFileHandler sfh = new StaticFileHandler(path, filesystemRoot, directoryIndex);
        hs.createContext(path, sfh);
    }

    public void handle(HttpExchange he) throws IOException {
        String method = he.getRequestMethod(); 
        if (! ("HEAD".equals(method) || "GET".equals(method))) {
            sendError(he, 501, "Unsupported HTTP method");
            return;
        }

        String wholeUrlPath = he.getRequestURI().getPath();
        if (wholeUrlPath.endsWith("/")) {
            wholeUrlPath += directoryIndex;
        }
        if (! wholeUrlPath.startsWith(urlPrefix)) {
            throw new RuntimeException("Path is not in prefix - incorrect routing?");
        }
        String urlPath = wholeUrlPath.substring(urlPrefix.length());

        File f = new File(filesystemRoot, urlPath);
        File canonicalFile;
        try {
            canonicalFile = f.getCanonicalFile();
        } catch (IOException e) {
            // This may be more benign (i.e. not an attack, just a 403),
            // but we don't want the attacker to be able to discern the difference.
            reportPathTraversal(he);
            return;
        }

        String canonicalPath = canonicalFile.getPath();
        if (! canonicalPath.startsWith(filesystemRoot)) {
            reportPathTraversal(he);
            return;
        }

        FileInputStream fis;
        try {
            fis = new FileInputStream(canonicalFile);
        } catch (FileNotFoundException e) {
            // The file may also be forbidden to us instead of missing, but we're leaking less information this way 
            sendError(he, 404, "File not found"); 
            return;
        }

        String mimeType = lookupMime(urlPath);
        he.getResponseHeaders().set("Content-Type", mimeType);
        if ("GET".equals(method)) {
            he.sendResponseHeaders(200, canonicalFile.length());            
            OutputStream os = he.getResponseBody();
            copyStream(fis, os);
            os.close();
        } else {
            assert("HEAD".equals(method));
            he.sendResponseHeaders(200, -1);
        }
        fis.close();
    }

    private void copyStream(InputStream is, OutputStream os) throws IOException {
        byte[] buf = new byte[4096];
        int n;
        while ((n = is.read(buf)) >= 0) {
            os.write(buf, 0, n);
        }
    }

    private void sendError(HttpExchange he, int rCode, String description) throws IOException {
        String message = "HTTP error " + rCode + ": " + description;
        byte[] messageBytes = message.getBytes("UTF-8");

        he.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
        he.sendResponseHeaders(rCode, messageBytes.length);
        OutputStream os = he.getResponseBody();
        os.write(messageBytes);
        os.close();
    }

    // This is one function to avoid giving away where we failed 
    private void reportPathTraversal(HttpExchange he) throws IOException {
        sendError(he, 400, "Path traversal attempt detected");
    }

    private static String getExt(String path) {
        int slashIndex = path.lastIndexOf('/');
        String basename = (slashIndex < 0) ? path : path.substring(slashIndex + 1);

        int dotIndex = basename.lastIndexOf('.');
        if (dotIndex >= 0) {
            return basename.substring(dotIndex + 1);
        } else {
            return "";
        }
    }

    private static String lookupMime(String path) {
        String ext = getExt(path).toLowerCase();
        return MIME_MAP.getOrDefault(ext, "application/octet-stream");
    }
}