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

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


当前回答

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

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

其他回答

我玩得很开心,我玩转了一下,拼凑出了这个。我希望这对你有所帮助。 你需要安装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/

这个代码比我们的代码更好,你只需要添加2个库:javax. servlet .jar和org.mortbay.jetty.jar。

类码头:

package jetty;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.mortbay.http.SocketListener;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.ServletHttpContext;

public class Jetty {

    public static void main(String[] args) {
        try {
            Server server = new Server();
            SocketListener listener = new SocketListener();      

            System.out.println("Max Thread :" + listener.getMaxThreads() + " Min Thread :" + listener.getMinThreads());

            listener.setHost("localhost");
            listener.setPort(8070);
            listener.setMinThreads(5);
            listener.setMaxThreads(250);
            server.addListener(listener);            

            ServletHttpContext context = (ServletHttpContext) server.getContext("/");
            context.addServlet("/MO", "jetty.HelloWorldServlet");

            server.start();
            server.join();

        /*//We will create our server running at http://localhost:8070
        Server server = new Server();
        server.addListener(":8070");

        //We will deploy our servlet to the server at the path '/'
        //it will be available at http://localhost:8070
        ServletHttpContext context = (ServletHttpContext) server.getContext("/");
        context.addServlet("/MO", "jetty.HelloWorldServlet");

        server.start();
        */

        } catch (Exception ex) {
            Logger.getLogger(Jetty.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
} 

Servlet类:

package jetty;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloWorldServlet extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException
    {
        String appid = httpServletRequest.getParameter("appid");
        String conta = httpServletRequest.getParameter("conta");

        System.out.println("Appid : "+appid);
        System.out.println("Conta : "+conta);

        httpServletResponse.setContentType("text/plain");
        PrintWriter out = httpServletResponse.getWriter();
        out.println("Hello World!");
        out.close();
    }
}

Spark是最简单的,这里有一个快速入门指南:http://sparkjava.com/

曾经有一段时间,我正在寻找类似的东西——一个轻量级但功能齐全的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.

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

从Java 11开始,旧的com.sun.net.httpserver再次成为一个公开且被接受的API。你可以把它作为HttpServer类,作为jdk的一部分。httpserver模块。参见https://docs.oracle.com/en/java/javase/11/docs/api/jdk.httpserver/com/sun/net/httpserver/HttpServer.html

这个类实现了一个简单的HTTP服务器。HttpServer绑定到一个IP地址和端口号,并侦听来自该地址上客户端的传入TCP连接。子类httpserver实现了一个处理HTTPS请求的服务器。

因此,除了它的局限性之外,没有理由再避免使用它了。

我使用它在服务器应用程序中发布控件接口。从客户端请求读取User-agent头,我甚至以文本/纯方式响应CLI工具,如curl或以更优雅的HTML方式响应任何其他浏览器。

很酷很简单。