是否有一种方法可以使用Java SE API在Java中创建一个非常基本的HTTP服务器(只支持GET/POST),而不需要编写代码手动解析HTTP请求和手动格式化HTTP响应?Java SE API在HttpURLConnection中很好地封装了HTTP客户机功能,但是是否有类似的HTTP服务器功能呢?

需要明确的是,我在网上看到的许多ServerSocket示例的问题是,它们自己进行请求解析/响应格式化和错误处理,这很乏味,容易出错,而且不太全面,出于这些原因,我试图避免使用它。


当前回答

我玩得很开心,我玩转了一下,拼凑出了这个。我希望这对你有所帮助。 你需要安装Gradle或者使用Maven插件。

build.gradle

plugins {
    id 'application'
}

group 'foo.bar'
version '1.0'

repositories {
    mavenCentral()
}

application{
    mainClass.set("foo.FooServer")
}

dependencies {}

FooServer 主入口点,你的主类。

package foo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FooServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(7654);
        serverSocket.setPerformancePreferences(0, 1, 2);

        /* the higher the numbers, the better the concurrent performance, ha!
           we found that a 3:7 ratio to be optimal
           3 partitioned executors to 7 network executors */

        ExecutorService executors = Executors.newFixedThreadPool(3);
        executors.execute(new PartitionedExecutor(serverSocket));
    }


    public static class PartitionedExecutor implements Runnable {
        ServerSocket serverSocket;

        public PartitionedExecutor(ServerSocket serverSocket) {
            this.serverSocket = serverSocket;
        }

        @Override
        public void run() {
            ExecutorService executors = Executors.newFixedThreadPool(30);
            executors.execute(new NetworkRequestExecutor(serverSocket, executors));
        }
    }


    public static class NetworkRequestExecutor implements Runnable{

        String IGNORE_CHROME = "/favicon.ico";
        String BREAK = "\r\n";
        String DOUBLEBREAK = "\r\n\r\n";

        Integer REQUEST_METHOD = 0;
        Integer REQUEST_PATH = 1;
        Integer REQUEST_VERSION = 2;

        String RENDERER;

        Socket socketClient;
        ExecutorService executors;
        ServerSocket serverSocket;

        public NetworkRequestExecutor(ServerSocket serverSocket, ExecutorService executors){
            this.serverSocket = serverSocket;
            this.executors = executors;
        }

        @Override
        public void run() {
            try {

                socketClient = serverSocket.accept();
                Thread.sleep(19);//do this for safari, its a hack but safari requires something like this.
                InputStream requestInputStream = socketClient.getInputStream();

                OutputStream clientOutput = socketClient.getOutputStream();

                if (requestInputStream.available() == 0) {
                    requestInputStream.close();
                    clientOutput.flush();
                    clientOutput.close();
                    executors.execute(new NetworkRequestExecutor(serverSocket, executors));
                    return;
                }

                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int bytesRead;
                while ((bytesRead = requestInputStream.read(byteBuffer.array())) != -1) {
                    byteArrayOutputStream.write(byteBuffer.array(), 0, bytesRead);
                    if (requestInputStream.available() == 0) break;
                }

                String completeRequestContent = byteArrayOutputStream.toString();
                String[] requestBlocks = completeRequestContent.split(DOUBLEBREAK, 2);

                String headerComponent = requestBlocks[0];
                String[] methodPathComponentsLookup = headerComponent.split(BREAK);
                String methodPathComponent = methodPathComponentsLookup[0];

                String[] methodPathVersionComponents = methodPathComponent.split("\\s");

                String requestVerb = methodPathVersionComponents[REQUEST_METHOD];
                String requestPath = methodPathVersionComponents[REQUEST_PATH];
                String requestVersion = methodPathVersionComponents[REQUEST_VERSION];


                if (requestPath.equals(IGNORE_CHROME)) {
                    requestInputStream.close();
                    clientOutput.flush();
                    clientOutput.close();
                    executors.execute(new NetworkRequestExecutor(serverSocket, executors));
                    return;
                }

                ConcurrentMap<String, String> headers = new ConcurrentHashMap<>();
                String[] headerComponents = headerComponent.split(BREAK);
                for (String headerLine : headerComponents) {
                    String[] headerLineComponents = headerLine.split(":");
                    if (headerLineComponents.length == 2) {
                        String fieldKey = headerLineComponents[0].trim();
                        String content = headerLineComponents[1].trim();
                        headers.put(fieldKey.toLowerCase(), content);
                    }
                }

                clientOutput.write("HTTP/1.1 200 OK".getBytes());
                clientOutput.write(BREAK.getBytes());

                Integer bytesLength = "hi".length();
                String contentLengthBytes = "Content-Length:" + bytesLength;
                clientOutput.write(contentLengthBytes.getBytes());
                clientOutput.write(BREAK.getBytes());

                clientOutput.write("Server: foo server".getBytes());
                clientOutput.write(BREAK.getBytes());

                clientOutput.write("Content-Type: text/html".getBytes());

                clientOutput.write(DOUBLEBREAK.getBytes());
                clientOutput.write("hi".getBytes());

                clientOutput.close();
                socketClient.close();

                executors.execute(new NetworkRequestExecutor(serverSocket, executors));

            } catch (IOException ex) {
                ex.printStackTrace();
            } catch (InterruptedException ioException) {
                ioException.printStackTrace();
            }
        }
    }
}

运行该程序:

gradle run

浏览:

http://localhost:7654/

其他回答

看看“Jetty”web服务器Jetty。一流的开源软件,似乎可以满足您的所有要求。

如果你坚持要创建自己的类,那么可以看看“httpMessage”类。

曾经有一段时间,我正在寻找类似的东西——一个轻量级但功能齐全的HTTP服务器,我可以轻松地嵌入和定制。我发现了两种可能的解决方案:

不是那么轻量级或简单的完整服务器(对于轻量级的极端定义)。 真正的轻量级服务器不是HTTP服务器,而是华丽的ServerSocket示例,甚至不远程兼容rfc,不支持通常需要的基本功能。

所以…我开始编写JLHTTP——Java轻量级HTTP服务器。

您可以将它作为单个(如果相当长)源文件嵌入到任何项目中,或者作为一个~50K的jar (~35K剥离),没有依赖关系。它努力与rfc兼容,包括大量的文档和许多有用的特性,同时将膨胀保持在最低限度。

Features include: virtual hosts, file serving from disk, mime type mappings via standard mime.types file, directory index generation, welcome files, support for all HTTP methods, conditional ETags and If-* header support, chunked transfer encoding, gzip/deflate compression, basic HTTPS (as provided by the JVM), partial content (download continuation), multipart/form-data handling for file uploads, multiple context handlers via API or annotations, parameter parsing (query string or x-www-form-urlencoded body), etc.

我希望其他人会觉得有用:-)

查看NanoHttpd

NanoHTTPD是一个轻量级的HTTP服务器,设计用于嵌入其他应用程序,在Modified BSD许可下发布。

它正在Github开发,并使用Apache Maven进行构建和单元测试。”

从Java SE 6开始,在Sun Oracle JRE中有一个内置的HTTP服务器。Java 9模块名称为jdk.httpserver。httpserver包摘要概述了涉及的类并包含示例。

这里有一个从他们的文档复制粘贴的启动示例。你可以复制,粘贴,然后在Java 6+上运行。 (尽管如此,所有试图编辑它的人,因为它是一段丑陋的代码,请不要,这是一个复制粘贴,不是我的,此外,你不应该编辑引文,除非它们在原始来源中发生了变化)

package com.stackoverflow.q3732109; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class Test { public static void main(String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0); server.createContext("/test", new MyHandler()); server.setExecutor(null); // creates a default executor server.start(); } static class MyHandler implements HttpHandler { @Override public void handle(HttpExchange t) throws IOException { String response = "This is the response"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } }

应该注意的是,他们示例中的response.length()部分是坏的,它应该是response.getBytes().length。即使这样,getBytes()方法也必须显式地指定在响应头中指定的字符集。唉,尽管对初学者有误导,但毕竟这只是一个基本的启动示例。

执行它并访问http://localhost:8000/test,你将看到以下响应:

这是反应


As to using com.sun.* classes, do note that this is, in contrary to what some developers think, absolutely not forbidden by the well known FAQ Why Developers Should Not Write Programs That Call 'sun' Packages. That FAQ concerns the sun.* package (such as sun.misc.BASE64Encoder) for internal usage by the Oracle JRE (which would thus kill your application when you run it on a different JRE), not the com.sun.* package. Sun/Oracle also just develop software on top of the Java SE API themselves like as every other company such as Apache and so on. Moreover, this specific HttpServer must be present in every JDK so there is absolutely no means of "portability" issue like as would happen with sun.* package. Using com.sun.* classes is only discouraged (but not forbidden) when it concerns an implementation of a certain Java API, such as GlassFish (Java EE impl), Mojarra (JSF impl), Jersey (JAX-RS impl), etc.

一个非常基本的HTTP服务器在TCP套接字级别的例子:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class NaiveHttpServer {

  public static void main(String[] args) throws IOException {
    String hostname = InetAddress.getLocalHost().getHostName();
    ServerSocket serverSocket = new ServerSocket(8089);
    while (true) {
      Socket clientSocket = serverSocket.accept();
      PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
      BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
      String s = in.readLine();
      System.out.println(s);
      while ("\r\n".equals(in.readLine())); 
      if ("GET /hostname HTTP/1.1".equals(s)) {
        out.println("HTTP/1.1 200 OK");
        out.println("Connection: close");
        out.println("Content-Type: text/plain");
        out.println("Content-Length:" + hostname.length());
        out.println();
        out.println(hostname);
      } else {
        out.println("HTTP/1.1 404 Not Found");
        out.println("Connection: close");
        out.println();    
      }
      out.flush();
    }
  }
}

该示例提供计算机的主机名。