有没有一种方法可以在Java中使用Java SE API创建一个非常基本的HTTP服务器(仅支持GET / POST),而无需编写手动解析HTTP请求和手动格式化HTTP响应的代码? Java SE API很好地封装了HttpURLConnection
中的HTTP客户端功能,但是否存在类似于HTTP服务器功能的模拟器?
只是为了明确,我对许多在线ServerSocket
示例的问题在于它们自己进行请求解析/响应格式化和错误处理,这是繁琐、容易出错且不太全面的,我正试图避免出现这些问题。
有没有一种方法可以在Java中使用Java SE API创建一个非常基本的HTTP服务器(仅支持GET / POST),而无需编写手动解析HTTP请求和手动格式化HTTP响应的代码? Java SE API很好地封装了HttpURLConnection
中的HTTP客户端功能,但是否存在类似于HTTP服务器功能的模拟器?
只是为了明确,我对许多在线ServerSocket
示例的问题在于它们自己进行请求解析/响应格式化和错误处理,这是繁琐、容易出错且不太全面的,我正试图避免出现这些问题。
jdk.httpserver
。 com.sun.net.httpserver
package summary概述了相关类并提供了示例。
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,您将看到以下响应:
这是响应
关于使用com.sun.*
类,请注意,与一些开发人员认为的相反,这绝对不被禁止在众所周知的FAQWhy Developers Should Not Write Programs That Call 'sun' Packages中。该FAQ涉及Oracle JRE内部使用的sun.*
包(例如sun.misc.BASE64Encoder
),如果在不同的JRE上运行它,则会杀死您的应用程序,而不是com.sun.*
包。Sun/Oracle也仅像Apache等其他公司一样在Java SE API之上开发软件。此外,每个JDK中都必须存在此特定的HttpServer
,因此不存在像使用sun.*
包时可能发生的“可移植性”问题。当涉及某个Java API的实现(例如GlassFish(Java EE实现),Mojarra(JSF实现),Jersey(JAX-RS实现)等)时,仅在禁止使用com.sun.*
类时才会受到鼓励(但不是禁止)。
sun.*
和com.sun.*
。例如,你能找到有关sun.*
API的任何文档吗?请看这里:http://java.sun.com/products/jdk/faq/faq-sun-packages.html 它有关于com.sun.*
的内容吗?com.sun.*
只是用于他们自己的公共软件,而不是Java API的一部分。他们也开发基于Java API的软件,就像其他公司一样。 - BalusC@jdk.Exported
,这意味着该API被视为公共的,并且将在Java 9上可用(由于Project Jigsaw,一些其他的com.sun.*
包将变得不可用)。 - Jules看看 NanoHttpd
NanoHTTPD是一个轻量级的HTTP服务器,旨在嵌入其他应用程序中,发布在修改后的BSD许可下。
它正在Github上开发,并使用Apache Maven进行构建和单元测试。
GET /../../blahblah http/1.1
这样的请求,攻击者可以越过网站根目录并进入系统文件领域,从而获取可用于破坏或远程攻击系统的文件(例如密码文件)。 - Lawrence Dolcom.sun.net.httpserver 解决方案在不同的JRE上不具备可移植性。更好的方式是使用javax.xml.ws中的官方Web服务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);
}
}
text/xml
。 - icza我很喜欢这个问题,因为这是一个持续创新的领域,并且在谈论小型(更小)设备中的嵌入式服务器时总是需要轻量级服务器。我认为答案可以分为两类。
虽然我可能会认为像 Jetty , Apache Http Components , Netty 和其他HTTP库更像原始的HTTP处理工具。标签是非常主观的,并取决于您为小型网站提供的内容类型。我根据问题的精神进行这种区分,特别是关于...
这些原始工具可以让您执行此操作(如其他回答中所述)。它们确实不太适合快速制作轻型、嵌入式或迷你服务器。迷你服务器是可以为您提供类似于完全功能的Web服务器(例如, Tomcat )的功能,没有花哨的东西,低容量,99%的时间性能良好。一个轻服务器似乎比原始的短语更接近,可能具有有限的子集功能,足以使您在90%的时间内看起来很好。我的原始想法是,如果/当您达到WAR文件的级别时,我们已经离开了“小”对于看起来像大服务器的盆景服务器。
轻服务器选项:
看一下“Jetty” Web服务器 Jetty。这是一个非常棒的开源软件,似乎满足您所有的要求。
如果您坚持自己编写,则可以查看“httpMessage”类。
server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool());
使用执行器服务通过多个线程允许多个请求处理。
因此,最终的代码将类似于以下内容:
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 App {
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/test", new MyHandler());
//Thread control is given to executor service.
server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool());
server.start();
}
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange t) throws IOException {
String response = "This is the response";
long threadId = Thread.currentThread().getId();
System.out.println("I am thread " + threadId );
response = response + "Thread Id = "+threadId;
t.sendResponseHeaders(200, response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
}
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();
}
}
import java.io.*;
import java.lang.reflect.*;
import java.net.InetSocketAddress;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
@SuppressWarnings("deprecation")
public class VerySimpleServletHttpServer {
HttpServer server;
private String contextPath;
private HttpHandler httpHandler;
public VerySimpleServletHttpServer(String contextPath, HttpServlet servlet) {
this.contextPath = contextPath;
httpHandler = new HttpHandlerWithServletSupport(servlet);
}
public void start(int port) throws IOException {
InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
server = HttpServer.create(inetSocketAddress, 0);
server.createContext(contextPath, httpHandler);
server.setExecutor(null);
server.start();
}
public void stop(int secondsDelay) {
server.stop(secondsDelay);
}
public int getServerPort() {
return server.getAddress().getPort();
}
}
final class HttpHandlerWithServletSupport implements HttpHandler {
private HttpServlet servlet;
private final class RequestWrapper extends HttpServletRequestWrapper {
private final HttpExchange ex;
private final Map<String, String[]> postData;
private final ServletInputStream is;
private final Map<String, Object> attributes = new HashMap<>();
private RequestWrapper(HttpServletRequest request, HttpExchange ex, Map<String, String[]> postData, ServletInputStream is) {
super(request);
this.ex = ex;
this.postData = postData;
this.is = is;
}
@Override
public String getHeader(String name) {
return ex.getRequestHeaders().getFirst(name);
}
@Override
public Enumeration<String> getHeaders(String name) {
return new Vector<String>(ex.getRequestHeaders().get(name)).elements();
}
@Override
public Enumeration<String> getHeaderNames() {
return new Vector<String>(ex.getRequestHeaders().keySet()).elements();
}
@Override
public Object getAttribute(String name) {
return attributes.get(name);
}
@Override
public void setAttribute(String name, Object o) {
this.attributes.put(name, o);
}
@Override
public Enumeration<String> getAttributeNames() {
return new Vector<String>(attributes.keySet()).elements();
}
@Override
public String getMethod() {
return ex.getRequestMethod();
}
@Override
public ServletInputStream getInputStream() throws IOException {
return is;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public String getPathInfo() {
return ex.getRequestURI().getPath();
}
@Override
public String getParameter(String name) {
String[] arr = postData.get(name);
return arr != null ? (arr.length > 1 ? Arrays.toString(arr) : arr[0]) : null;
}
@Override
public Map<String, String[]> getParameterMap() {
return postData;
}
@Override
public Enumeration<String> getParameterNames() {
return new Vector<String>(postData.keySet()).elements();
}
}
private final class ResponseWrapper extends HttpServletResponseWrapper {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final ServletOutputStream servletOutputStream = new ServletOutputStream() {
@Override
public void write(int b) throws IOException {
outputStream.write(b);
}
};
private final HttpExchange ex;
private final PrintWriter printWriter;
private int status = HttpServletResponse.SC_OK;
private ResponseWrapper(HttpServletResponse response, HttpExchange ex) {
super(response);
this.ex = ex;
printWriter = new PrintWriter(servletOutputStream);
}
@Override
public void setContentType(String type) {
ex.getResponseHeaders().add("Content-Type", type);
}
@Override
public void setHeader(String name, String value) {
ex.getResponseHeaders().add(name, value);
}
@Override
public javax.servlet.ServletOutputStream getOutputStream() throws IOException {
return servletOutputStream;
}
@Override
public void setContentLength(int len) {
ex.getResponseHeaders().add("Content-Length", len + "");
}
@Override
public void setStatus(int status) {
this.status = status;
}
@Override
public void sendError(int sc, String msg) throws IOException {
this.status = sc;
if (msg != null) {
printWriter.write(msg);
}
}
@Override
public void sendError(int sc) throws IOException {
sendError(sc, null);
}
@Override
public PrintWriter getWriter() throws IOException {
return printWriter;
}
public void complete() throws IOException {
try {
printWriter.flush();
ex.sendResponseHeaders(status, outputStream.size());
if (outputStream.size() > 0) {
ex.getResponseBody().write(outputStream.toByteArray());
}
ex.getResponseBody().flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
ex.close();
}
}
}
public HttpHandlerWithServletSupport(HttpServlet servlet) {
this.servlet = servlet;
}
@SuppressWarnings("deprecation")
@Override
public void handle(final HttpExchange ex) throws IOException {
byte[] inBytes = getBytes(ex.getRequestBody());
ex.getRequestBody().close();
final ByteArrayInputStream newInput = new ByteArrayInputStream(inBytes);
final ServletInputStream is = new ServletInputStream() {
@Override
public int read() throws IOException {
return newInput.read();
}
};
Map<String, String[]> parsePostData = new HashMap<>();
try {
parsePostData.putAll(HttpUtils.parseQueryString(ex.getRequestURI().getQuery()));
// check if any postdata to parse
parsePostData.putAll(HttpUtils.parsePostData(inBytes.length, is));
} catch (IllegalArgumentException e) {
// no postData - just reset inputstream
newInput.reset();
}
final Map<String, String[]> postData = parsePostData;
RequestWrapper req = new RequestWrapper(createUnimplementAdapter(HttpServletRequest.class), ex, postData, is);
ResponseWrapper resp = new ResponseWrapper(createUnimplementAdapter(HttpServletResponse.class), ex);
try {
servlet.service(req, resp);
resp.complete();
} catch (ServletException e) {
throw new IOException(e);
}
}
private static byte[] getBytes(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
while (true) {
int r = in.read(buffer);
if (r == -1)
break;
out.write(buffer, 0, r);
}
return out.toByteArray();
}
@SuppressWarnings("unchecked")
private static <T> T createUnimplementAdapter(Class<T> httpServletApi) {
class UnimplementedHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
throw new UnsupportedOperationException("Not implemented: " + method + ", args=" + Arrays.toString(args));
}
}
return (T) Proxy.newProxyInstance(UnimplementedHandler.class.getClassLoader(),
new Class<?>[] { httpServletApi },
new UnimplementedHandler());
}
}