-
package com.http; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 此服务支持图片静态资源请求、get、post(application/x-www-form-urlencoded)方法 * post中的其它编码格式multipart/form-data、application/json暂时未实现 */ public class HttpServer implements Runnable { @Override public void run() { ServerSocket socket = null; Socket clientSocket=null; ExecutorService executor = Executors.newCachedThreadPool(); try { socket = new ServerSocket(Constants.SERVER_PORT); while(true) { clientSocket = socket.accept(); System.out.println("有新的连接接入"); executor.submit(new MyHttpHandler(clientSocket));//开户启一个新线程处理新的连接请求,一个线程可以处理同一条连接上的多个请求,而不是每个请求创建一个连接 } } catch (IOException e) { e.printStackTrace(); System.out.println("启动http 服务器失败 端口:" + Constants.SERVER_PORT); System.exit(1); } } public static void main(String[] args){ System.out.println("服务已启动"); System.out.println("测试get http://localhost:1234/ http://localhost:1234/path?name=value"); System.out.println("测试post http://localhost:1234/ http://localhost:1234/path?name=value 注意此方法需要自建表单或使用postman测试"); System.out.println("测试图片请求 http://localhost:1234/static/1.png"); new HttpServer().run(); } }
-
package com.http; import java.io.IOException; import java.net.Socket; public class MyHttpHandler extends HttpHandler { public MyHttpHandler(Socket socket) { super(socket); } @Override public void get(HttpRequest httpRequest, HttpResponse httpResponse) { try { if (httpRequest.getRequestPath().equals("/")) { httpResponse.sendHtml("<html>\n" + "\t<head>\n" + "\t\t<meta charset=\"utf-8\" />\n" + "\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n" + "\t\t<title>主页</title>\n" + "\t</head>\n" + "\t<body>\n" + "返回网页内容省略..." + "\t</body>\n" + "</html>"); } else { httpResponse.sendPlain("返回普通文字 请求路径:"+httpRequest.getRequestPath()); } } catch (IOException e) { e.printStackTrace(); } } @Override public void post(HttpRequest httpRequest, HttpResponse httpResponse) { try { httpResponse.sendPlain("post 请求路径:"+httpRequest.getRequestPath()); } catch (IOException e) { e.printStackTrace(); } } }
-
package com.http; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.ProtocolException; import java.net.Socket; import java.net.SocketException; public abstract class HttpHandler implements Runnable { private Socket clientSocket; public HttpHandler(Socket socket) { this.clientSocket = socket; } @Override public void run() { HttpRequest httpRequest = null; HttpResponse httpResponse = null; try { //当浏览器主动断开链接或关闭浏览器时,HttpRequst会抛出ProtocolException跳出循环,并关闭clientSocket //同一个clientSocket连接,可能会发送多个请求,也就是http1.1默认的长连接Connection: keepAlive //服务器默认不会主动断开连接,什么时候断开由浏览器决定,一般来说当网页中的最后一个资源请求完毕,等一会没有新请求就关闭此连接 while (true) { httpRequest = new HttpRequest(clientSocket); httpResponse = new HttpResponse(clientSocket); handleRequest(httpRequest, httpResponse); } } catch (SocketException e) { e.printStackTrace(); } catch (ProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } finally { try { clientSocket.close(); System.out.println("java.net.SocketException socket顺利close()"); } catch (IOException e1) { e1.printStackTrace(); System.out.println("java.net.SocketException socket close()不顺利"); } } } public void handleRequest(HttpRequest httpRequest, HttpResponse httpResponse) { if (RequestStatusLine.GET.equalsIgnoreCase(httpRequest.getMethod())) { if (httpRequest.getRequestPath().indexOf(Constants.REQUEST_RESOURCE_PATH) == 1) { //处理静态资源请求,图片、word文档、pdf... handlerStaticResource(httpRequest, httpResponse); } else { //处理正常get请求 get(httpRequest, httpResponse); } } else if (RequestStatusLine.POST.equalsIgnoreCase(httpRequest.getMethod())) { post(httpRequest, httpResponse); } else { throw new RuntimeException("暂时不支持请求方法 " + httpRequest.getMethod()); } } public abstract void get(HttpRequest httpRequest, HttpResponse httpResponse); public abstract void post(HttpRequest httpRequest, HttpResponse httpResponse); /** * 处理静态资源请求,需要提前配置静态资源路径Constants.REQUEST_RESOURCE_PATH 的值 * @param httpRequest * @param httpResponse */ private void handlerStaticResource(HttpRequest httpRequest, HttpResponse httpResponse) { System.out.println("请求的是资源文件"); String fileName = httpRequest.getRequestPath().substring(8); File file = new File(Constants.LOCAL_RESOURCE_PATH + File.separator + fileName); byte[] bytes = new byte[1024000]; try { FileInputStream inputStream = new FileInputStream(file); //定位资源文件并转换成stream BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); //增强文件读取速度,添加缓冲区 httpResponse.sendMetadata(HttpResponse.checkContentType(fileName),file.length(),false);//1.先发送响应报文头信息 int length; while ((length=bufferedInputStream.read(bytes))!=-1){ //2. 后发送dataBody System.out.println("length="+length); httpResponse.send(bytes,0,length); } // inputStream.close(); bufferedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
-
package com.http; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.ProtocolException; import java.net.Socket; import java.net.SocketException; import java.util.HashMap; import java.util.Map; public class HttpRequest { private RequestStatusLine requestStatusLine; private Map<String, String> headers; private byte[] bodyData; private InputStream inputStream; static int CACHE_MAX_LENGTH = 10240; //缓存header信息10kb够大了吧 public HttpRequest(Socket socket) throws IOException { //读取接收的stream // 1. 读取header和requestLine this.inputStream= socket.getInputStream(); BufferedInputStream bin = new BufferedInputStream(inputStream); String metadata = generateMetadata(bin); parseMetadata(metadata); System.out.println("demo/common/http/HttpRequest.java:32 "+Thread.currentThread().getName()+" "+getRequestPath()); //如果是get请求,按照http协议body是没有数据的 if (getMethod().equalsIgnoreCase(RequestStatusLine.GET)) { return;} // 2. 读取requestBody byte[] bodyData = new byte[getContentLength()]; for (int i = 0; i < bodyData.length; i++) { bodyData[i] = (byte) bin.read(); } String contentType = getContentType(); switch (contentType) { case "application/x-www-form-urlencoded": this.bodyData=bodyData; String requestParameter = new String(bodyData); System.out.println("requestParameter " + requestParameter); break; case "multipart/form-data": throw new RuntimeException("不支持的post multipart/form-data 编码类型 等待重写"); // break; case "application/json": throw new RuntimeException("不支持的post application/json 编码类型 等待重写"); // break; case "text/xml": throw new RuntimeException("不支持的post text/xml 编码类型 等待重写"); // break; default: throw new RuntimeException("不支持的请求方法"); } } public RequestStatusLine getRequestStatusLine() { return requestStatusLine; } public Map<String, String> getHeaders() { return headers; } public byte[] getBodyData() { return bodyData; } private String generateMetadata(BufferedInputStream bis) throws SocketException,IOException { byte[] cache = new byte[CACHE_MAX_LENGTH]; int read; int index = 0; String metadata = null; while ((read = bis.read()) != -1) { if ((read == '\r' && bis.read() == '\n') || (read == '\n' && bis.read() == '\r')) { cache[index] = '\r'; cache[++index] = '\n'; if ((cache[index - 2] == '\n' && cache[index - 3] == '\r') || (cache[index - 3] == '\r' && cache[index - 2] == '\n')) { //当连续读取到\r\n\r\n四个字符时,就表示http协议报文结束了,也可以设置读取超时抛出异常防止所谓的ddos攻击 return metadata = new String(cache, 0, index + 1); } } else { cache[index] = (byte) read; } index++; } bis.close(); //当浏览器主动断开socket连接时,79行bis.read()将不再堵塞并返回-1, // 如果访问者不是浏览器而是一个java.net.Socket,此Socket对象调用close()方法也会触发bis.read()解除堵塞并返回-1 throw new ProtocolException("客户断主动开链接"+Thread.currentThread().getName()); } private void parseMetadata(String metadata) { String[] metaLine = metadata.split("\r\n"); Map<String, String> headers = new HashMap<>(); for (int i = 0; i < metaLine.length; i++) { if (i == 0) { //解析请求行 String[] requestLine = metaLine[0].split(" "); this.requestStatusLine = new RequestStatusLine(requestLine[0], requestLine[1], requestLine[2]); } else { //解析header String[] header = metaLine[i].split(": "); headers.put(header[0].toLowerCase(), header[1]); } } this.headers = headers; } public String getMethod() { return requestStatusLine.getMethod(); } public int getContentLength() { String content_length = headers.get("content-length"); return content_length == null ? 0 : Integer.parseInt(content_length); } public String getContentType() { String content_type = headers.get("content-type"); if (content_type == null) { throw new RuntimeException("http请求没有指定Content-Type类型"); } return content_type; } public String getRequestPath() { return requestStatusLine.getRequestPath(); } public InputStream getInputStream() { return inputStream; } }
-
package com.http; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; public class HttpResponse { static String TEXT_HTML="text/html"; static String TEXT_PLAIN="text/plain"; static String IMAGE_JPG="image/jpg"; static String IMAGE_JPEG="image/jpeg"; static String IMAGE_PNG="image/png"; static String DEFAULT_CHARSET="UTF-8"; private OutputStream out; public HttpResponse(Socket clientSocket) throws IOException { this.out=clientSocket.getOutputStream(); } public void sendPlain(String text) throws IOException { send(TEXT_PLAIN ,text); } public void sendHtml(String html) throws IOException { send(TEXT_HTML ,html); } /** * 主要用于发送图片、文档 * @param contentType * @param body * @throws IOException */ public void sendStaticResource(String contentType, byte[] body) throws IOException { send(contentType,body,false); } public void sendMetadata(String contentType,long bodyLength,boolean useUtf8) throws IOException { //制作响应报文 StringBuffer response = new StringBuffer(); //响应状态行 response.append("HTTP/1.0 200 OK\r\n"); //响应头... // response.append("Content-type: "+contentType+"; charset=UTF-8\r\n"); // response.append("Content-type: "+contentType+"\r\n"); response.append(useUtf8 ? "Content-type: "+contentType+"; charset=UTF-8\r\n" : "Content-type: "+contentType+"\r\n"); response.append("Content-Length: "+bodyLength+"\r\n"); response.append("Connection: keep-alive\r\n"); response.append("\r\n"); //返回响应头信息 out.write(response.toString().getBytes()); } private void send(String contentType, byte[] body,boolean useUtf8) throws IOException { //返回响应头信息 sendMetadata( contentType,body.length, useUtf8); //返回body内容 out.write(body); } private void send(String contentType,String body) throws IOException { send(contentType,body.getBytes(),true); } public void send(byte b) throws IOException { out.write(b); } public void send(byte[] bytes, int off, int len) throws IOException { out.write(bytes,off,len); } public void close(){ try { out.close(); } catch (IOException e) { e.printStackTrace(); } } public static String checkContentType(String fileName){ switch(fileName.substring(fileName.lastIndexOf('.'))){ case ".png": return IMAGE_PNG; case ".jpg": return IMAGE_JPG; case ".jpeg": return IMAGE_JPEG; } return ""; } }
-
package com.http; import java.util.HashMap; import java.util.Map; public class RequestStatusLine { public static String GET="get"; public static String POST="post"; private String method; //暂时只支持 get , post private String requestPath="/"; //默认"/" private String protocol; // HTTP/1.0 , HTTP/1.1 Map<String, String> queryParameters; public String getMethod() { return method; } public String getRequestPath() { return requestPath; } public String getProtocol() { return protocol; } public RequestStatusLine(){} public RequestStatusLine(String method, String requestPath, String protocol) { this.method = method; this.protocol = protocol; this.queryParameters = parseQueryString(requestPath); } @Override public String toString() { return method+" "+requestPath+" "+protocol; } public Map<String, String> parseQueryString(String url) { Map<String, String> map = new HashMap<>(); String[] pathAndparameter = url.split("\\?"); this.requestPath=pathAndparameter[0]; if (pathAndparameter.length<=1){ return null; } String[] nameValuePairs = pathAndparameter[1].split("&"); for (int i = 0; i < nameValuePairs.length; i++) { int index = nameValuePairs[i].indexOf('='); String name = nameValuePairs[i].substring(0, index); String value = nameValuePairs[i].substring(index + 1); map.put(name, value); } return map; } }
-
package com.http; public class ResponseStatusLine { private String protocol; // HTTP/1.0 , HTTP/1.1 private int statusCode; //200,302,404,500 private String statusStr; // OK public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public int getStatusCode() { return statusCode; } public void setStatusCode(int statusCode) { this.statusCode = statusCode; } public String getStatusStr() { return statusStr; } public void setStatusStr(String statusStr) { this.statusStr = statusStr; } public ResponseStatusLine(){} public ResponseStatusLine(String protocol, int statusCode, String statusStr) { this.protocol = protocol; this.statusCode = statusCode; this.statusStr = statusStr; } @Override public String toString() { return protocol+" "+statusCode+" "+statusStr; } }
-
package com.http; public class Constants { public static final String LOCAL_RESOURCE_PATH = "F:\\image\\other"; /** * http://localhost:1234/static/photo.png * 此路径下的请求全部返回文件资源 * @return */ public static final String REQUEST_RESOURCE_PATH = "static"; public static final int SERVER_PORT = 1234; }
Socket实现轻量级HTTP服务器
最新推荐文章于 2024-03-21 17:14:07 发布