标签:根据 参数 single 线程池 定义 buffere oid https 客户端
作为一个Java后端,提供HTTP服务可以说是基本技能之一,但是你真的理解HTTP协议吗?你知道如何使用HTTP服务器吗?Tomcat的底层如何支持HTTP服务?什么是著名的servlet以及如何使用它?
套接字编程是您第一次学习Java时不可回避的一章;虽然在实际的业务项目中,使用Socket的可能性基本上是零,但该博客将主要介绍如何使用Socket来实现简单的HTTP服务器功能,提供常见的GoAP/POST请求支持,然后在PRO中了解HTTP协议。塞斯。
因为我们的目标是构建一个带有套接字的HTTP服务器,所以我们需要首先确认两点:如何使用套接字;如何使用HTTP协议以及如何解析数据;下面分别解释。
这里我们主要使用服务器套接字绑定端口,提供TCP服务,基本上使用姿态比较简单,一般的例行程序如下
对应的伪代码如下:
ServerSocket serverSocket = new ServerSocket(port, ip) serverSocket.accept(); // 接收请求数据 socket.getInputStream(); // 返回数据给请求方 out = socket.getOutputStream() out.print(xxx) out.flush();; // 关闭连接 socket.close()
我们上面的serversocket是TCP协议,HTTP协议本身是TCP协议之上的一层,对于我们创建一个HTTP服务器来说,最需要注意的只有两点。
所以我们需要知道数据格式的规范了
请求消息
响应消息
以上两张图片,先有直观的图像,然后开始对焦。
无论是请求消息还是相应的消息,都可以分为三个部分,这大大简化了我们的后续处理。
现在我们来谈谈重点。基于套接字创建HTTP服务器不是一个大问题。我们需要注意以下几点。
我们从套接字获取所有数据,并将其解析为相应的HTTP请求。首先,我们定义一个请求对象并在其中存储一些基本的HTTP信息。接下来,我们将重点从套接字中提取所有数据,并将其封装为请求对象。
1 @Data 2 public static class Request { 3 /** 4 * 请求方法 GET/POST/PUT/DELETE/OPTION... 5 */ 6 private String method; 7 /** 8 * 请求的uri 9 */ 10 private String uri; 11 /** 12 * http版本 13 */ 14 private String version; 15 16 /** 17 * 请求头 18 */ 19 private Map<String, String> headers; 20 21 /** 22 * 请求参数相关 23 */ 24 private String message; 25 }
根据前面的HTTP协议,解析过程如下。让我们先看看请求行的解析过程。
请求行包含三个基本元素:请求方法+uri+http版本,用空格分隔,所以解析代码如下
1 /** 2 * 根据标准的http协议,解析请求行 3 * 4 * @param reader 5 * @param request 6 */ 7 private static void decodeRequestLine(BufferedReader reader, Request request) throws IOException { 8 String[] strs = StringUtils.split(reader.readLine(), " "); 9 assert strs.length == 3; 10 request.setMethod(strs[0]); 11 request.setUri(strs[1]); 12 request.setVersion(strs[2]); 13 }
从第二行到第一行的请求头解析为请求头,请求头格式清晰,如key:value,实现如下。
1 /** 2 * 根据标准http协议,解析请求头 3 * 4 * @param reader 5 * @param request 6 * @throws IOException 7 */ 8 private static void decodeRequestHeader(BufferedReader reader, Request request) throws IOException { 9 Map<String, String> headers = new HashMap<>(16); 10 String line = reader.readLine(); 11 String[] kv; 12 while (!"".equals(line)) { 13 kv = StringUtils.split(line, ":"); 14 assert kv.length == 2; 15 headers.put(kv[0].trim(), kv[1].trim()); 16 line = reader.readLine(); 17 } 18 19 request.setHeaders(headers); 20 }
最后,对文本的解析,这篇文章需要注意的是,文本可能是空的,也可能是数据;当有数据时,我们如何取出所有的数据?
首先看下面的具体实现
1 /** 2 * 根据标注http协议,解析正文 3 * 4 * @param reader 5 * @param request 6 * @throws IOException 7 */ 8 private static void decodeRequestMessage(BufferedReader reader, Request request) throws IOException { 9 int contentLen = Integer.parseInt(request.getHeaders().getOrDefault("Content-Length", "0")); 10 if (contentLen == 0) { 11 // 表示没有message,直接返回 12 // 如get/options请求就没有message 13 return; 14 } 15 16 char[] message = new char[contentLen]; 17 reader.read(message); 18 request.setMessage(new String(message)); 19 }
注意我上面的姿势。首先,我们根据请求头中的内容类型值获取主体的数据大小。所以我们通过创建一个如此大的char[]来获得它,我们可以读取流中的所有数据。如果数组小于实际大小,则无法完成读取。如果它很大,数组中会有一些空数据。
最后,封装上述解析以完成请求解析。
1 /** 2 * http的请求可以分为三部分 3 * 4 * 第一行为请求行: 即 方法 + URI + 版本 5 * 第二部分到一个空行为止,表示请求头 6 * 空行 7 * 第三部分为接下来所有的,表示发送的内容,message-body;其长度由请求头中的 Content-Length 决定 8 * 9 * 几个实例如下 10 * 11 * @param reqStream 12 * @return 13 */ 14 public static Request parse2request(InputStream reqStream) throws IOException { 15 BufferedReader httpReader = new BufferedReader(new InputStreamReader(reqStream, "UTF-8")); 16 Request httpRequest = new Request(); 17 decodeRequestLine(httpReader, httpRequest); 18 decodeRequestHeader(httpReader, httpRequest); 19 decodeRequestMessage(httpReader, httpRequest); 20 return httpRequest; 21 }
每个请求都分配了一个任务来单独完成这项任务,即支持并发性,对于serversocket,接收到一个请求,然后创建一个http task任务来实现HTTP通信。
那么这个httptask是做什么的呢?
1 public class HttpTask implements Runnable { 2 private Socket socket; 3 4 public HttpTask(Socket socket) { 5 this.socket = socket; 6 } 7 8 @Override 9 public void run() { 10 if (socket == null) { 11 throw new IllegalArgumentException("socket can‘t be null."); 12 } 13 14 try { 15 OutputStream outputStream = socket.getOutputStream(); 16 PrintWriter out = new PrintWriter(outputStream); 17 18 HttpMessageParser.Request httpRequest = HttpMessageParser.parse2request(socket.getInputStream()); 19 try { 20 // 根据请求结果进行响应,省略返回 21 String result = ...; 22 String httpRes = HttpMessageParser.buildResponse(httpRequest, result); 23 out.print(httpRes); 24 } catch (Exception e) { 25 String httpRes = HttpMessageParser.buildResponse(httpRequest, e.toString()); 26 out.print(httpRes); 27 } 28 out.flush(); 29 } catch (IOException e) { 30 e.printStackTrace(); 31 } finally { 32 try { 33 socket.close(); 34 } catch (IOException e) { 35 e.printStackTrace(); 36 } 37 } 38 } 39 }
对于请求结果的封装,给一个简单的进行演示
1 @Data 2 public static class Response { 3 private String version; 4 private int code; 5 private String status; 6 7 private Map<String, String> headers; 8 9 private String message; 10 } 11 12 public static String buildResponse(Request request, String response) { 13 Response httpResponse = new Response(); 14 httpResponse.setCode(200); 15 httpResponse.setStatus("ok"); 16 httpResponse.setVersion(request.getVersion()); 17 18 Map<String, String> headers = new HashMap<>(); 19 headers.put("Content-Type", "application/json"); 20 headers.put("Content-Length", String.valueOf(response.getBytes().length)); 21 httpResponse.setHeaders(headers); 22 23 httpResponse.setMessage(response); 24 25 StringBuilder builder = new StringBuilder(); 26 buildResponseLine(httpResponse, builder); 27 buildResponseHeaders(httpResponse, builder); 28 buildResponseMessage(httpResponse, builder); 29 return builder.toString(); 30 } 31 32 33 private static void buildResponseLine(Response response, StringBuilder stringBuilder) { 34 stringBuilder.append(response.getVersion()).append(" ").append(response.getCode()).append(" ") 35 .append(response.getStatus()).append("\n"); 36 } 37 38 private static void buildResponseHeaders(Response response, StringBuilder stringBuilder) { 39 for (Map.Entry<String, String> entry : response.getHeaders().entrySet()) { 40 stringBuilder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n"); 41 } 42 stringBuilder.append("\n"); 43 } 44 45 private static void buildResponseMessage(Response response, StringBuilder stringBuilder) { 46 stringBuilder.append(response.getMessage()); 47 }
基本上,我们已经做了所有我们需要做的事情,剩下的很简单。创建serversocket,绑定端口以接收请求,然后在线程池中运行此HTTP服务。
1 public class BasicHttpServer { 2 private static ExecutorService bootstrapExecutor = Executors.newSingleThreadExecutor(); 3 private static ExecutorService taskExecutor; 4 private static int PORT = 8999; 5 6 static void startHttpServer() { 7 int nThreads = Runtime.getRuntime().availableProcessors(); 8 taskExecutor = 9 new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100), 10 new ThreadPoolExecutor.DiscardPolicy()); 11 12 while (true) { 13 try { 14 ServerSocket serverSocket = new ServerSocket(PORT); 15 bootstrapExecutor.submit(new ServerThread(serverSocket)); 16 break; 17 } catch (Exception e) { 18 try { 19 //重试 20 TimeUnit.SECONDS.sleep(10); 21 } catch (InterruptedException ie) { 22 Thread.currentThread().interrupt(); 23 } 24 } 25 } 26 27 bootstrapExecutor.shutdown(); 28 } 29 30 private static class ServerThread implements Runnable { 31 32 private ServerSocket serverSocket; 33 34 public ServerThread(ServerSocket s) throws IOException { 35 this.serverSocket = s; 36 } 37 38 @Override 39 public void run() { 40 while (true) { 41 try { 42 Socket socket = this.serverSocket.accept(); 43 HttpTask eventTask = new HttpTask(socket); 44 taskExecutor.submit(eventTask); 45 } catch (Exception e) { 46 e.printStackTrace(); 47 try { 48 TimeUnit.SECONDS.sleep(1); 49 } catch (InterruptedException ie) { 50 Thread.currentThread().interrupt(); 51 } 52 } 53 } 54 } 55 } 56 }
此时,一个基于socket的HTTP服务器基本上已经构建好,可以进行测试了。
此服务器主要基于项目快速修复。本项目主要解决应用程序内部服务访问和数据修改问题。我们在这个项目的基础上进行测试。
完成的POST请求如下
接下来我们看下打印出返回头的情况
标签:根据 参数 single 线程池 定义 buffere oid https 客户端
原文地址:https://www.cnblogs.com/aishangJava/p/10218402.html