是否有一种方法可以使用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/

其他回答

我强烈建议考虑Simple,特别是如果您不需要Servlet功能,而只是访问请求/响应对象。如果你需要REST,你可以把Jersey放在上面,如果你需要输出HTML或类似的东西,有Freemarker。我真的很喜欢使用这个组合可以做的事情,而且需要学习的API相对较少。

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

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

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

从Java 18开始,你可以用Java标准库创建简单的web服务器:

class Main {
    public static void main(String[] args) {
        var port = 8000;
        var rootDirectory = Path.of("C:/Users/Mahozad/Desktop/");
        var outputLevel = OutputLevel.VERBOSE;
        var server = SimpleFileServer.createFileServer(
                new InetSocketAddress(port),
                rootDirectory,
                outputLevel
        );
        server.start();
    }
}

默认情况下,这将显示指定根目录的目录列表。您可以将index.html文件(以及CSS和JS文件等其他资产)放在该目录中来显示它们。

示例(我把这些放在桌面上,上面指定为我的根目录):

index . html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Java 18 Simple Web Server</title>
  <link rel="stylesheet" href="styles.css">
  <style>h1 { color: blue; }</style>
  <script src="scripts.js" defer>
    let element = document.getElementsByTagName("h1")[0];
    element.style.fontSize = "48px";
  </script>
</head>
<body>
  <h1>I'm <i>index.html</i> in the root directory.</h1>
</body>
</html>

旁注

对于Java标准库HTTP客户端,请参阅Java 11新HTTP客户端API。

这个代码比我们的代码更好,你只需要添加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();
    }
}