現在、市場に出回っているほとんどの C/S モード取引システムには、一般に 2 つの対話方式があります。 1 つ目は、クライアントとサーバー間の通信モジュールとして JAVA Socket または JAVA NIO Socket を使用し、メッセージ構造を自分で編成してトランザクション情報の送受信を完了する方法です。この開発モードは複雑であり、開発者がシステム自体の通信モジュール、メッセージのデータ構造、暗号化モジュールなど。 2 つ目は、JAVA で最も一般的に使用される Tomcat である http サーバーを使用することです。クライアントとサーバーは、http メッセージを通じて通信します。開発者は、サーバーとクライアント間のリンクの詳細を気にする必要はありません。 http メッセージ構造のデータを保持し、標準に従ってメッセージの送受信を処理する必要があります。 ただし、このモデルでは、開発者が追加のミドルウェアである Tomcat を保守する必要があります。 仕事中に、http リクエストとレスポンス メッセージを自分で構築して解析する方法を説明した記事をたまたま目にしたので、Java NIO ベースの http サーバーを完成させるために、自分の NIO サーバー上で http メッセージを構築して解析することにしました。通信モジュールの実装内容を習得し、業務モジュールの開発を簡素化できる httpリクエストとレスポンスを処理するだけで済むと同時に、サービスを開始するmainメソッドを実装できるため、依存性とメンテナンスが軽減されますTomcatで。 実際には、Tomcat の簡易版を実装することになりますが、このサービスの実用性とセキュリティは当然ながら Tomcat とは比較にならないため、このモデルは興味のある学生との学習とコミュニケーションにのみ使用されます。 まず、HTTP リクエスト ヘッダー情報をカプセル化し、それを解析してオブジェクトに保存します: package niosrv; import java.io.BufferedReader; import java.io.IOException; /** * Parse and save HttpHead details */ public class NioHttpHead { public static final String ACCEPT = "Accept"; public static final String ACCEPT_LANGUAGE = "Accept-Language"; public static final String USER_AGENT = "User-Agent"; public static final String ACCEPT_ENCODING = "Accept-Encoding"; public static final String HOST = "Host"; public static final String DNT = "DNT"; public static final String CONNECTION = "Connection"; public static final String COOKIE = "Cookie"; private BufferedReader br; private String method; private String requestURL; private String protocol; private String agent; private String host; private int port; private String encoding; private String language; private String accept; private String dnt; private String connection; private String cookie; private String headStr; private String contentStr = ""; public NioHttpHead(BufferedReader br) { this.br = br; } public void parseHead() { String s = null; String content = ""; try { s = br.readLine(); String[] firstLine = s.split(" "); if (firstLine.length == 3) { this.method = firstLine[0].trim(); this.requestURL = firstLine[1].trim(); this.protocol = firstLine[2].trim(); } StringBuffer headBuffer = new StringBuffer(s + "\r\n"); s = br.readLine(); while (s != null && !s.equals("")) { String[] split = s.split(":"); switch (split[0].trim()) { case ACCEPT: { this.accept = split[1].trim(); } case ACCEPT_LANGUAGE: { this.language = split[1].trim(); break; } case USER_AGENT: { this.agent = split[1].trim(); break; } case ACCEPT_ENCODING: { this.encoding = split[1].trim(); break; } case HOST: { this.host = split[1].trim(); break; } case DNT: { this.dnt = split[1].trim(); break; } case CONNECTION: { this.connection = split[1].trim(); break; } case COOKIE: { this.cookie = split[1].trim(); break; } } s = br.readLine(); headBuffer.append(s + "\r\n"); } this.headStr = headBuffer.toString(); //content parse, put request content into content attribute content = br.readLine(); if (content != null && !content.equals("")) { StringBuffer contentBuffer = new StringBuffer(content); while (content != null && !content.equals("")) { content = br.readLine(); if (content != null && !content.equals("")) { contentBuffer.append(content); } } this.contentStr = contentBuffer.toString(); } System.out.println("Request HEAD message format:" + h eadBuffer.toString()); System.out.println("Request BODY message format:" + contentStr.toString()); } catch (IOException e) { e.printStackTrace(); System.out.println("Request HEAD message parse error:", e); System.out.println("Request BODY message parse error:", e); } } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getRequestURL() { return requestURL; } public void setRequestURL(String requestURL) { this.requestURL = requestURL; } public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public String getAgent() { return agent; } public void setAgent(String agent) { this.agent = agent; } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getEncoding() { return encoding; } public void setEncoding(String encoding) { this.encoding = encoding; } public String getLanguage() { return language; } public void setLanguage(String language) { this.language = language; } public String getAccept() { return accept; } public void setAccept(String accept) { this.accept = accept; } public String getDnt() { return dnt; } public void setDnt(String dnt) { this.dnt = dnt; } public String getConnection() { return connection; } public void setConnection(String connection) { this.connection = connection; } public String getCookie() { return cookie; } public void setCookie(String cookie) { this.cookie = cookie; } public String getHeadStr() { return headStr; } public void setHeadStr(String headStr) { this.headStr = headStr; } public String getContentStr() { return contentStr; } public void setContentStr(String contentStr) { this.contentStr = contentStr; } } Java で Writer クラスを書き換えて、NIO Socket経由で応答情報をクライアントに送り返します: package niosrv; import java.io.IOException; import java.io.Writer; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; public class NioHttpOutWriter extends Writer { //because of the pictures size are always too big, so the buffer size here is big ByteBuffer buffer = ByteBuffer.allocate(1024000); private SocketChannel channel; private Selector selector; private SelectionKey key; private String successHead; public NioHttpOutWriter(String successHeader, SelectionKey key, SocketChannel channel, Selector selector) { this.successHead = successHeader; this.key = key; this.channel = channel; this.selector = selector; } @Override public void write(char[] contentChar, int off, int len) throws IOException { System.out.println("Write Back By write(1,2,3)!"); String content = new String(contentChar); StringBuffer respStr = new StringBuffer(this.successHead.toString()); respStr.append(content); byte[] bytesBody = respStr.toString().getBytes(); buffer.put(bytesBody); buffer.flip(); channel.write(buffer); channel.register(selector, SelectionKey.OP_WRITE, this); key.cancel(); channel.shutdownOutput(); channel.close(); } @Override public void flush() throws IOException { } @Override public void close() throws IOException { } public ByteBuffer getBuffer() { return buffer; } public void setBuffer(ByteBuffer buffer) { this.buffer = buffer; } } Java で PrintWriter クラスを書き換えて、応答情報を JSON や暗号化された暗号文などのテキスト形式でクライアントに送り返します: package niosrv; import java.io.PrintWriter; import java.io.Writer; public class NioHttpPrtWriter extends PrintWriter { public NioHttpPrtWriter(Writer write) { super(write); } public void writeAsString(String content) throws Exception { super.write(content.toCharArray(), 0, content.length()); } @Override public void print(String s) { super.print(s); } } サーブレットに HttpServletRequest インターフェイスを実装し、独自の NIO Http Request リクエスト処理クラスを実装します。この実装クラスでは 3 つのアクションを実行します: 1,NIO Socket経由で HTTP メッセージを受信します。 2, メッセージ ヘッダーを解析し、メッセージ ヘッダーとメッセージ本文を別々に保存します。 3,メッセージ本文を解析して、メッセージ内のリクエスト パラメーターを取得します。リクエスト パラメーターは、JSON モード、URL アセンブリ、パラメーターなしモードの 3 つのモードをサポートします。 package niosrv; import com.alibaba.fastjson.JSONObject; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.security.Principal; import java.util.*; public class NioHttpRequest implements HttpServletRequest { private SocketChannel channel; private NioHttpHead head; private Map<String, String> params = new HashMap<>(); private String context; public byte[] reqContent; private SelectionKey key; public NioHttpRequest(SelectionKey key) { this.key = key; this.channel = (SocketChannel) this.key.channel(); } public boolean parseHead(String projectName) throws Exception { String receive = receive(channel); if (null == receive || ("").equals(receive)) { return false; } //parse the request head BufferedReader br = new BufferedReader(new StringReader(receive)); this.head = new NioHttpHead(br); this.head.parseHead(); br.close(); //parse the request content String content = this.head.getContentStr(); boolean flag = parseParams(projectName, content); return true && flag; } private boolean parseParams(String projectName, String content) { String url = this.head.getRequestURL(); //Block illegal request URLs if (!url.substring(1).startsWith(projectName)) { return false; } //The parameters are concatenated with the original URL if (url.indexOf("?") > 0) { this.context = url.substring(0, url.indexOf("?")); String params = url.substring(url.indexOf("?") + 1, url.length()); String[] split = params.split("&"); for (String param : split) { String[] split2 = param.split("="); this.params.put(split2[0], split2[1]); } reqContent = params.getBytes(); //The parameter is stream mode, the structure is JSON or original mode x=1&y=2 } else if (content != null && !("").equals(content)) { this.context = url.substring(url.indexOf("/"), url.length()); try { if ((!content.contains("{") && !content.contains("}")) || content.indexOf("&") > 0) { String[] split = content.split("&"); for (String param : split) { String[] split2 = param.split("="); this.params.put(split2[0], split2[1]); } } else { JSONObject json = JSONObject.parseObject(content); for (String key : json.keySet()) { this.params.put(key.toString(), json.get(key).toString()); } } reqContent = content.getBytes(); } catch (Exception e) { e.printStackTrace(); return false; } } //no parameters this.context = url.substring(url.indexOf("/"), url.length()); return true; } private String receive(SocketChannel socketChannel) throws Exception { byte[] bytes = null; ByteBuffer receiveBuffer = null; ByteBuffer buffer = ByteBuffer.allocate(1024000); if (socketChannel.isOpen() && socketChannel.isConnected()) { int nBytes = 0; buffer.clear(); try { nBytes = socketChannel.read(buffer); } catch (IOException e) { System.out.println("Socket Channel Can Not Read Buffer From Request,Socket Closed By Client In Exception!"); //If the client is closed abnormally, it will also be closed here key.cancel(); if (channel.isOpen()) { channel.shutdownOutput(); channel.close(); } return null; } buffer.flip();//open state if (nBytes > 0) { receiveBuffer = ByteBuffer.allocate(nBytes); receiveBuffer.clear(); buffer.get(receiveBuffer.array()); receiveBuffer.flip(); } if (receiveBuffer != null) { bytes = receiveBuffer.array(); return new String(bytes); } else { System.out.println("Socket Channel Can Not Read Buffer From Request,Socket Channel Closed By Client Unexpected!"); //If the client is closed abnormally, it will also be closed here key.channel().close(); key.cancel(); if (channel.isOpen()) { channel.shutdownOutput(); channel.close(); } return null; } } else { System.out.println("Socket Channel Closed By Client Unexpected,Interrupt The Thread immidiately,!"); return null; } } public byte[] getReqContent() { return reqContent; } @Override public String getParameter(String arg0) { return this.params.get(arg0); } @Override public String getContextPath() { this.context = context.substring(1, context.length()); if (this.context.indexOf("/") > 0) { return this.context.substring(0, context.indexOf("/")); } else { return this.context; } } @Override public String getServletPath() { if (this.context.indexOf("/") == 2) { return this.context.substring(context.indexOf("/") + 1, context.lastIndexOf("/")); } else { return ""; } } public String getNioHead() { if (null == head) { return "[error http head]"; } else if (null == head.getHeadStr()) { return "[error http head]"; } else { return head.getHeadStr(); } } @Override public String getAuthType() { return null; } @Override public Cookie[] getCookies() { return new Cookie[0]; } @Override public long getDateHeader(String s) { return 0; } @Override public String getHeader(String s) { return null; } @Override public Enumeration<String> getHeaders(String s) { return null; } @Override public Enumeration<String> getHeaderNames() { return null; } @Override public int getIntHeader(String s) { return 0; } @Override public String getMethod() { return this.head.getMethod(); } @Override public String getPathInfo() { return null; } @Override public String getPathTranslated() { return null; } @Override public String getQueryString() { return null; } @Override public String getRemoteUser() { return null; } @Override public boolean isUserInRole(String s) { return false; } @Override public Principal getUserPrincipal() { return null; } @Override public String getRequestedSessionId() { return null; } @Override public String getRequestURI() { String wholeUrl = this.head.getRequestURL(); if (wholeUrl.contains("?")) { return wholeUrl.substring(wholeUrl.lastIndexOf("/") + 1, wholeUrl.indexOf("?")); } else { return wholeUrl.substring(wholeUrl.lastIndexOf("/") + 1); } } @Override public StringBuffer getRequestURL() { return new StringBuffer(this.head.getRequestURL()); } @Override public HttpSession getSession(boolean b) { return null; } @Override public HttpSession getSession() { return null; } @Override public String changeSessionId() { return null; } @Override public boolean isRequestedSessionIdValid() { return false; } @Override public boolean isRequestedSessionIdFromCookie() { return false; } @Override public boolean isRequestedSessionIdFromURL() { return false; } @Override public boolean isRequestedSessionIdFromUrl() { return false; } @Override public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { return false; } @Override public void login(String s, String s2) throws ServletException { } @Override public void logout() throws ServletException { } @Override public Collection<Part> getParts() throws IOException, ServletException { return null; } @Override public Part getPart(String s) throws IOException, ServletException { return null; } @Override public <T extends HttpUpgradeHandler> T upgrade(Class<T> tClass) throws IOException, ServletException { return null; } @Override public Object getAttribute(String s) { return null; } @Override public Enumeration<String> getAttributeNames() { return null; } @Override public String getCharacterEncoding() { return null; } @Override public void setCharacterEncoding(String s) throws UnsupportedEncodingException { } @Override public int getContentLength() { if (reqContent == null) { return 0; } else { String content = new String(reqContent); return content.length(); } } @Override public long getContentLengthLong() { return 0; } @Override public String getContentType() { return null; } @Override public ServletInputStream getInputStream() throws IOException { return null; } @Override public Enumeration<String> getParameterNames() { return null; } @Override public String[] getParameterValues(String s) { return new String[0]; } @Override public Map<String, String[]> getParameterMap() { return null; } @Override public String getProtocol() { return this.head.getProtocol(); } @Override public String getScheme() { return null; } @Override public String getServerName() { return null; } @Override public int getServerPort() { return 0; } @Override public BufferedReader getReader() throws IOException { return null; } @Override public String getRemoteAddr() { return channel.socket().getInetAddress().getHostAddress(); } @Override public String getRemoteHost() { return channel.socket().getInetAddress().getHostAddress(); } @Override public void setAttribute(String s, Object o) { } @Override public void removeAttribute(String s) { } @Override public Locale getLocale() { return null; } @Override public Enumeration<Locale> getLocales() { return null; } @Override public boolean isSecure() { return false; } @Override public RequestDispatcher getRequestDispatcher(String s) { return null; } @Override public String getRealPath(String s) { return null; } @Override public int getRemotePort() { return 0; } @Override public String getLocalName() { return null; } @Override public String getLocalAddr() { return null; } @Override public int getLocalPort() { return 0; } @Override public ServletContext getServletContext() { return null; } @Override public AsyncContext startAsync() throws IllegalStateException { return null; } @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { return null; } @Override public boolean isAsyncStarted() { return false; } @Override public boolean isAsyncSupported() { return false; } @Override public AsyncContext getAsyncContext() { return null; } @Override public DispatcherType getDispatcherType() { return null; } } サーブレットに HttpServletResponse インターフェースを実装し、独自の NIO Http Response 応答処理クラスを実装し、主に対応する Http クラスで応答の Http ヘッドを構築し、それが応答コンテンツとともにクライアントに書き戻されるのを待ちます: package niosrv; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.text.SimpleDateFormat; import java.util.*; public class NioHttpResponse implements HttpServletResponse { private SocketChannel channel; private SelectionKey key; private Selector selector; public static final int HTTP_STATUS_SUCCESS = 200; public static final int HTTP_STATUS_NOT_FOUND = 404; //head message private final Map<String, String> headers = new HashMap<String, String>(); private int responseCode = HTTP_STATUS_SUCCESS; private String responseInfo = "OK"; private int responseError = HTTP_STATUS_NOT_FOUND; private String responseErrInfo = "SYS_ERROR"; private String successHeader; public NioHttpResponse(SelectionKey key, Selector selector) { this.key = key; this.channel = (SocketChannel) this.key.channel(); this.selector = selector; setUpHeader(); } private void setUpHeader() { headers.put("Server", "HttpServer (Nio HttpServer 1.0)"); headers.put("User-Agent", "*/*"); headers.put("Content-Type", getContentType()); headers.put("Connection", "keep-alive"); String date = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.US).format(new Date()); headers.put("Date", date); } private String header2String(int resCode) { StringBuffer head = new StringBuffer("HTTP/1.1"); head.append(" ").append(resCode == 0 ? responseCode : responseError).append(" ").append(resCode == 0 ? responseInfo : responseErrInfo).append("\r\n"); head.append("Date:").append(" ").append(headers.get("Date")).append("\r\n"); head.append("Server:").append(" ").append(headers.get("Server")).append("\r\n"); head.append("User-Agent:").append(" ").append(headers.get("User-Agent")).append("\r\n"); head.append("Content-Type:").append(" ").append(headers.get("Content-Type")).append("\r\n"); head.append("Connection:").append(" ").append(headers.get("Connection")).append("\r\n"); head.append("\r\n"); return head.toString(); } public void setContentType(String contentType) { headers.put("Content-Type", contentType); } public PrintWriter getWriter(int respCode) throws IOException { this.successHeader = header2String(respCode); return getWriter(); } @Override public PrintWriter getWriter() throws IOException { NioHttpPrtWriter printWrite = new NioHttpPrtWriter(new NioHttpOutWriter( this.successHeader, key, channel, selector)); return printWrite; } @Override public void addCookie(Cookie cookie) { } @Override public boolean containsHeader(String s) { return false; } @Override public String encodeURL(String s) { return null; } @Override public String encodeRedirectURL(String s) { return null; } @Override public String encodeUrl(String s) { return null; } @Override public String encodeRedirectUrl(String s) { return null; } @Override public void sendError(int i, String s) throws IOException { } @Override public void sendError(int i) throws IOException { } @Override public void sendRedirect(String s) throws IOException { } @Override public void setDateHeader(String s, long l) { } @Override public void addDateHeader(String s, long l) { } @Override public void setHeader(String s, String s2) { } @Override public void addHeader(String s, String s2) { } @Override public void setIntHeader(String s, int i) { } @Override public void addIntHeader(String s, int i) { } @Override public void setStatus(int i) { } @Override public void setStatus(int i, String s) { } @Override public int getStatus() { return 0; } @Override public String getHeader(String s) { return null; } @Override public Collection<String> getHeaders(String s) { return null; } @Override public Collection<String> getHeaderNames() { return null; } @Override public String getCharacterEncoding() { return null; } @Override public String getContentType() { return headers.get("Content-Type"); } @Override public ServletOutputStream getOutputStream() throws IOException { return null; } @Override public void setCharacterEncoding(String s) { } @Override public void setContentLength(int i) { } @Override public void setContentLengthLong(long l) { } @Override public void setBufferSize(int i) { } @Override public int getBufferSize() { return 0; } @Override public void flushBuffer() throws IOException { } @Override public void resetBuffer() { } @Override public boolean isCommitted() { return false; } @Override public void reset() { } @Override public void setLocale(Locale locale) { } @Override public Locale getLocale() { return null; } } 以上で第 1 セクションの内容となり、Nio を最下層として実装した Http リクエストとレスポンスの解析と構築が完了し、次のセクションに続きます。 文章导航 以Java NIO Socket为底层基础,手动解析HTTP请求与响应,实现类似Tomcat的定制化http服务器(一) Using Java NIO Socket as the underlying foundation, manually parse HTTP requests and responses, and implement a customized server similar to Tomcat (1)