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

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


当前回答

一个非常基本的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();
    }
  }
}

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

其他回答

您可以编写一个非常简单的嵌入式Jetty Java服务器。

嵌入式Jetty意味着服务器(Jetty)与应用程序一起发布,而不是将应用程序部署在外部Jetty服务器上。

因此,如果在非嵌入式方法中,你的webapp内置到WAR文件中,并部署到一些外部服务器(Tomcat / Jetty / etc),在嵌入式Jetty中,你在相同的代码库中编写webapp并实例化Jetty服务器。

一个嵌入式Jetty Java服务器的例子,你可以git克隆和使用:https://github.com/stas-slu/embedded-jetty-java-server-example

httpserver解决方案不能跨jre移植。最好使用javax.xml.ws中的官方webservices API来引导一个最小的HTTP服务器…

import java.io._
import javax.xml.ws._
import javax.xml.ws.http._
import javax.xml.transform._
import javax.xml.transform.stream._

@WebServiceProvider
@ServiceMode(value=Service.Mode.PAYLOAD) 
class P extends Provider[Source] {
  def invoke(source: Source) = new StreamSource( new StringReader("<p>Hello There!</p>"));
}

val address = "http://127.0.0.1:8080/"
Endpoint.create(HTTPBinding.HTTP_BINDING, new P()).publish(address)

println("Service running at "+address)
println("Type [CTRL]+[C] to quit!")

Thread.sleep(Long.MaxValue)

编辑:这实际上是工作!上面的代码看起来像Groovy之类的。以下是我测试的Java翻译:

import java.io.*;
import javax.xml.ws.*;
import javax.xml.ws.http.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

@WebServiceProvider
@ServiceMode(value = Service.Mode.PAYLOAD)
public class Server implements Provider<Source> {

    public Source invoke(Source request) {
        return  new StreamSource(new StringReader("<p>Hello There!</p>"));
    }

    public static void main(String[] args) throws InterruptedException {

        String address = "http://127.0.0.1:8080/";
        Endpoint.create(HTTPBinding.HTTP_BINDING, new Server()).publish(address);

        System.out.println("Service running at " + address);
        System.out.println("Type [CTRL]+[C] to quit!");

        Thread.sleep(Long.MAX_VALUE);
    }
}

这是我简单的web服务器,在JMeter中用于测试webhook(这就是为什么它会在收到请求后关闭并结束自己)。

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpServer {

    private static int extractContentLength(StringBuilder sb) {
        int length = 0;
        String[] lines = sb.toString().split("\\n");
        for (int i = 0; i < lines.length; i++) {
            String s = lines[i];
            if (s.toLowerCase().startsWith("Content-Length:".toLowerCase()) && i <= lines.length - 2) {
                String slength = s.substring(s.indexOf(":") + 1, s.length()).trim();
                length = Integer.parseInt(slength);
                System.out.println("Length = " + length);
                return length;
            }
        }
        return 0;
    }

    public static void main(String[] args) throws IOException {
        
        
        int port = Integer.parseInt(args[0]);
        System.out.println("starting HTTP Server on port " + port);

        StringBuilder outputString = new StringBuilder(1000);

        ServerSocket serverSocket = new ServerSocket(port);
        serverSocket.setSoTimeout(3 * 60 * 1000); // 3 minutes timeout
        while (true) {

            outputString.setLength(0); // reset buff

            Socket clientSocket = serverSocket.accept(); // blocking
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

            try {

                boolean isBodyRead = false;
                int dataBuffer;
                while ((dataBuffer = clientSocket.getInputStream().read()) != -1) {

                    if (dataBuffer == 13) { // CR
                        if (clientSocket.getInputStream().read() == 10) { // LF
                            outputString.append("\n");
                        }
                    } else {
                        outputString.append((char) dataBuffer);
                    }
                    
                    // do we have Content length
                    int len = extractContentLength(outputString);
                    if (len > 0) {
                        int actualLength = len - 1; // we need to substract \r\n
                        for (int i = 0; i < actualLength; i++) {
                            int body = clientSocket.getInputStream().read();
                            outputString.append((char) body);
                        }
                        isBodyRead = true;
                        break;
                    }

                } // end of reading while

                if (isBodyRead) {
                    // response headers
                    out.println("HTTP/1.1 200 OK");
                    out.println("Connection: close");
                    out.println(); // must have empty line for HTTP
                    
                    out.flush(); 
                    out.close(); // close clients connection
                }

            } catch (IOException ioEx) {
                System.out.println(ioEx.getMessage());
            }

            System.out.println(outputString.toString());
            break; // stop server - break while true
            
        } // end of outer while true
        
        serverSocket.close();

    } // end of method

}

你可以这样测试:

curl -X POST -H "Content-Type: application/json" -H "Connection: close" -d '{"name": "gustinmi", "email": "gustinmi at google dot com "}' -v http://localhost:8081/

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

从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。