是否有一种方法可以使用Java SE API在Java中创建一个非常基本的HTTP服务器(只支持GET/POST),而不需要编写代码手动解析HTTP请求和手动格式化HTTP响应?Java SE API在HttpURLConnection中很好地封装了HTTP客户机功能,但是是否有类似的HTTP服务器功能呢?
需要明确的是,我在网上看到的许多ServerSocket示例的问题是,它们自己进行请求解析/响应格式化和错误处理,这很乏味,容易出错,而且不太全面,出于这些原因,我试图避免使用它。
是否有一种方法可以使用Java SE API在Java中创建一个非常基本的HTTP服务器(只支持GET/POST),而不需要编写代码手动解析HTTP请求和手动格式化HTTP响应?Java SE API在HttpURLConnection中很好地封装了HTTP客户机功能,但是是否有类似的HTTP服务器功能呢?
需要明确的是,我在网上看到的许多ServerSocket示例的问题是,它们自己进行请求解析/响应格式化和错误处理,这很乏味,容易出错,而且不太全面,出于这些原因,我试图避免使用它。
当前回答
这是我简单的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/
其他回答
我喜欢这个问题,因为这是一个不断创新的领域,总是需要有一个轻型服务器,特别是当谈到小型设备中的嵌入式服务器时。我认为答案可以分为两大类。
瘦服务器:使用最少处理、上下文或会话处理的服务器式静态内容。 小型服务器:从表面上看,a具有许多类似httpd的服务器特性,并且占用的空间尽可能小。
虽然我可能会认为HTTP库,如:Jetty, Apache HTTP Components, Netty和其他更像一个原始的HTTP处理工具。标签是非常主观的,取决于你被要求为小网站提供的东西的种类。我是根据问题的精神,特别是关于……
"...无需编写代码手动解析HTTP请求和手动格式化HTTP响应……”
These raw tools let you do that (as described in other answers). They don't really lend themselves to a ready-set-go style of making a light, embedded or mini-server. A mini-server is something that can give you similar functionality to a full-function web server (like say, Tomcat) without bells and whistles, low volume, good performance 99% of the time. A thin-server seems closer to the original phrasing just a bit more than raw perhaps with a limited subset functionality, enough to make you look good 90% of the time. My idea of raw would be makes me look good 75% - 89% of the time without extra design and coding. I think if/when you reach the level of WAR files, we've left the "small" for bonsi servers that looks like everything a big server does smaller.
瘦服务器选项
灰熊 UniRest(多语言) NanoHTTPD(只有一个文件)
Mini-server选项:
Spark Java…有了很多帮助器结构,比如过滤器、模板等,就有可能做得很好。 MadVoc……目标是成为盆景,而且很可能是这样;-)
Among the other things to consider, I'd include authentication, validation, internationalisation, using something like FreeMaker or other template tool to render page output. Otherwise managing HTML editing and parameterisation is likely to make working with HTTP look like noughts-n-crosses. Naturally it all depends on how flexible you need to be. If it's a menu-driven FAX machine it can be very simple. The more interactions, the 'thicker' your framework needs to be. Good question, good luck!
我玩得很开心,我玩转了一下,拼凑出了这个。我希望这对你有所帮助。 你需要安装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/
看看“Jetty”web服务器Jetty。一流的开源软件,似乎可以满足您的所有要求。
如果你坚持要创建自己的类,那么可以看看“httpMessage”类。