├── .gitignore ├── .travis.yml ├── README.md ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── ljm │ │ │ └── server │ │ │ ├── BootStrap.java │ │ │ ├── LifeCycle.java │ │ │ ├── Server.java │ │ │ ├── ServerFactory.java │ │ │ ├── ServerStatus.java │ │ │ ├── config │ │ │ └── ServerConfig.java │ │ │ ├── demo │ │ │ ├── EchoEventHandler.java │ │ │ ├── FileEventHandler.java │ │ │ ├── FileTransfer.java │ │ │ └── NIOEchoEventHandler.java │ │ │ ├── event │ │ │ ├── EventException.java │ │ │ ├── handler │ │ │ │ ├── AbstractEventHandler.java │ │ │ │ ├── EventHandler.java │ │ │ │ └── HandlerException.java │ │ │ └── listener │ │ │ │ ├── AbstractEventListener.java │ │ │ │ └── EventListener.java │ │ │ ├── impl │ │ │ └── SimpleServer.java │ │ │ ├── io │ │ │ ├── connection │ │ │ │ ├── ChannelConnection.java │ │ │ │ ├── Connection.java │ │ │ │ ├── channel │ │ │ │ │ ├── ChannelHelper.java │ │ │ │ │ └── SelectableChannelConnection.java │ │ │ │ └── socket │ │ │ │ │ └── SocketConnection.java │ │ │ ├── connector │ │ │ │ ├── AbstractChannelConnector.java │ │ │ │ ├── AbstractConnector.java │ │ │ │ ├── Connector.java │ │ │ │ ├── ConnectorException.java │ │ │ │ ├── ConnectorFactory.java │ │ │ │ └── impl │ │ │ │ │ ├── nio │ │ │ │ │ └── SocketChannelConnector.java │ │ │ │ │ └── socket │ │ │ │ │ ├── SocketConnector.java │ │ │ │ │ ├── SocketConnectorConfig.java │ │ │ │ │ └── SocketConnectorFactory.java │ │ │ ├── event │ │ │ │ └── listener │ │ │ │ │ └── impl │ │ │ │ │ ├── ConnectionEventListener.java │ │ │ │ │ └── SelectableChannleEventListener.java │ │ │ └── utils │ │ │ │ └── IoUtils.java │ │ │ └── protocol │ │ │ ├── Protocol.java │ │ │ └── http │ │ │ ├── ContentTypeUtil.java │ │ │ ├── HttpConstants.java │ │ │ ├── HttpMessage.java │ │ │ ├── HttpProtocol.java │ │ │ ├── HttpQueryParameter.java │ │ │ ├── HttpQueryParameters.java │ │ │ ├── HttpRequestMessage.java │ │ │ ├── HttpResponseMessage.java │ │ │ ├── RequestLine.java │ │ │ ├── ResponseLine.java │ │ │ ├── StartLine.java │ │ │ ├── body │ │ │ ├── HttpBody.java │ │ │ └── HttpBodyInputStream.java │ │ │ ├── handler │ │ │ ├── AbstractHttpEventHandler.java │ │ │ └── HttpStaticResourceEventHandler.java │ │ │ ├── header │ │ │ ├── HttpHeader.java │ │ │ ├── HttpMessageHeaders.java │ │ │ └── IMessageHeaders.java │ │ │ ├── parser │ │ │ ├── AbstractHttpRequestMessageParser.java │ │ │ ├── BodyInfo.java │ │ │ ├── DefaultHttpBodyParser.java │ │ │ ├── DefaultHttpHeaderParser.java │ │ │ ├── DefaultHttpQueryParameterParser.java │ │ │ ├── DefaultHttpRequestLineParser.java │ │ │ ├── DefaultHttpRequestMessageParser.java │ │ │ ├── HttpBodyParser.java │ │ │ ├── HttpHeaderParser.java │ │ │ ├── HttpParserContext.java │ │ │ ├── HttpQueryParameterParser.java │ │ │ ├── HttpRequestLineParser.java │ │ │ ├── HttpRequestMessageParser.java │ │ │ └── ParserException.java │ │ │ └── response │ │ │ ├── HttpResponseConstants.java │ │ │ ├── HttpResponseMessageBuilder.java │ │ │ ├── HttpResponseMessageWriter.java │ │ │ ├── HttpStatus.java │ │ │ └── ResponseLineConstants.java │ └── resources │ │ ├── log4j2.xml │ │ └── mime-mapping.properties └── test │ ├── java │ └── com │ │ └── ljm │ │ └── server │ │ ├── TestServer.java │ │ ├── TestServerAcceptRequest.java │ │ ├── TestServerBase.java │ │ ├── TestServerSuite.java │ │ ├── com │ │ └── ljm │ │ │ └── server │ │ │ └── protocol │ │ │ └── http │ │ │ ├── body │ │ │ └── TestHttpBodyInputStream.java │ │ │ └── response │ │ │ └── TestHttpResponseMessageBuilder.java │ │ ├── io │ │ └── connection │ │ │ └── socket │ │ │ └── TestSocketConnection.java │ │ └── protocol │ │ └── http │ │ └── parser │ │ ├── TestDefaultHttpBodyParser.java │ │ ├── TestDefaultHttpHeaderParser.java │ │ ├── TestDefaultHttpQueryParameterParser.java │ │ ├── TestDefaultHttpRequestLineParser.java │ │ └── TestDefaultHttpRequestMessageParser.java │ └── resources │ └── log4j2.xml └── web ├── index.html ├── index.js ├── main.css └── spring.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | target/ 25 | !.mvn/wrapper/maven-wrapper.jar 26 | 27 | ### STS ### 28 | .apt_generated 29 | .classpath 30 | .factorypath 31 | .project 32 | .settings 33 | .springBeans 34 | 35 | ### IntelliJ IDEA ### 36 | .idea 37 | *.iws 38 | *.iml 39 | *.ipr 40 | 41 | ### NetBeans ### 42 | nbproject/private/ 43 | build/ 44 | nbbuild/ 45 | dist/ 46 | nbdist/ 47 | .nb-gradle/ 48 | 49 | /protocol 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | install: true 4 | addons: 5 | sonarcloud: 6 | organization: "pkpk1234-github" 7 | token: 8 | secure: "cDSmt46DADc4bTWJmDhUDiZiaSg4KYcR1opU83/vIBtEcIv26G+5N3rZz6kr6/ZlZtKIwruMART7U9jVoxUDV6V0nbbOsYdLzHc5D9udrCrF641nk7YkkNYgRb1x6goOZvVplg/fYrfebwbv5OnSpGn/FCsuAGpevWaj2zMToOtyMa5Wd/LdG89NJNq57wv2ayWQuDgWYS1RMgkKrW6mUiD/1/W1eJ0W+LAjNsfBWcxIO/Cm9Uy+9b33jn5tfiVnxF3AVcEn8wDmLzXInDY+gaIQIQJlG8aFs4vpgHCqT6Ava8fAhV1hOpl1dS6bifnmghe6C8NXGKUTubH6FnzI5XX0MoPjcqCbT3NXNkaWb4AiX9bXRa+m5uEUx+yQS94KXtt8tqz7fTn4c2+aCqyT0T6c+tewpe/mDlOMOqlueOSSxAKMMuHogIQIGmv6roxhfgGzQaBX7dC7ibJ5cHHyT7uxsMIDjr/6XiPytB5ABpUOh6TCyBhJ1fsBL8sLwbU3hx9yCaPzxAG9ncJzSgadGL/rb99TI/DuZEOWWC8Xc+jv0gUuyJ88cAQgKBAn5Ssj5IEyhWh1v8GFtZWDDajulhCuyHanp5ucG9fzngCXcOF1ozN+D6kJuLyNvictkqMR+iiVtnYo4129apM6u/IM97Cuhg+6Um8eKQXoGd06jjU=" 9 | jdk: 10 | - oraclejdk8 11 | script: 12 | - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar 13 | cache: 14 | directories: 15 | - "$HOME/.m2/repository" 16 | - "$HOME/.sonar/cache" 17 | env: 18 | secure: DMGcOpVk1LQqEsLe6qoSeIAKLMVXk3p7MqWr9MgyUWxj+ZLH9i7exHBrqD3IxrUuoWTGdMba/gpZthNAct5v9uPddg8Rm6ckBXXot7Bz4wTF/yiT2wIdAa/5IBIZpH9oFlnrePAA36gmohqO/LRj2Mgf7yn7giK6KKkZ7HYwUH4VuW9G9yZ7Mv0sopZJMpRNa616GZ1SZZB6Kay84PnvltpqfZucwITjnrJd6+1RJxGDRI+SFPt2CN7dz1qccP+oyxG9Fmg0CW0m2x8/CEpHSX4z76R7HZ5JOxGPUa9yrfTgsuRTAcKU1j1ZuUz4xZh7LdGU1QF0581N0LntUiFw1CxItCLI3K/VpYl2We3+JG8tRH36uZXHTXhcPwSmrgObhSKhrP5iGcv0PKNZKEBquTrgB/DrMP9JkvQW6WkSsmJy77+sLOa0lD7A8slGc38r1831HKFfCbvPvxKUFY+kepFBr0bl2dCzEF+Jfq8w7hNA512HBlVzTSlGmT9GhpMs0/dCuaqB8bmnC4DYedPbdXybigpJCpqPQM3fmeFacYgemqr5462jlcMZbW0BGO7/i3MAZYXiqJglVgHMBNmOAhaiUmn+MkAJrFPLaFlnj3AvzOHFFKda7pIIlfgBL7hBbztFgEpU9Zmi4vUBh11xyBpqJg0VI5ocIBVVseXRdoM= 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeggarServletContainer 2 | [![Build Status](https://api.travis-ci.org/pkpk1234/BeggarServletContainer.svg?branch=master)](https://travis-ci.org/pkpk1234/BeggarServletContainer) 3 | [![SonarQube Bugs](https://sonarcloud.io/api/badges/measure?key=com.ljm%3Abegger-server&metric=bugs)](https://sonarcloud.io/dashboard?id=com.ljm%3Abegger-server) 4 | 5 | 6 | 乞丐版servlet容器 7 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.ljm 8 | begger-server 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 1.8 13 | ${java.version} 14 | ${java.version} 15 | UTF-8 16 | 1.7.25 17 | 2.10.0 18 | 3.7 19 | 1.10.19 20 | 20.0 21 | 2.5 22 | 23 | 24 | 25 | 26 | org.slf4j 27 | slf4j-api 28 | ${slf4j.version} 29 | 30 | 31 | org.apache.logging.log4j 32 | log4j-slf4j-impl 33 | ${log4j.version} 34 | 35 | 36 | org.apache.logging.log4j 37 | log4j-api 38 | ${log4j.version} 39 | 40 | 41 | org.apache.logging.log4j 42 | log4j-core 43 | ${log4j.version} 44 | 45 | 46 | org.apache.commons 47 | commons-lang3 48 | ${commons-lang3.version} 49 | 50 | 51 | junit 52 | junit 53 | 4.12 54 | test 55 | 56 | 57 | org.mockito 58 | mockito-core 59 | ${mockito.version} 60 | 61 | 62 | com.google.guava 63 | guava 64 | ${guava.version} 65 | 66 | 67 | commons-io 68 | commons-io 69 | ${commons-io.version} 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/BootStrap.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server; 2 | 3 | import com.ljm.server.config.ServerConfig; 4 | import com.ljm.server.demo.EchoEventHandler; 5 | import com.ljm.server.event.listener.EventListener; 6 | import com.ljm.server.io.connection.Connection; 7 | import com.ljm.server.io.connector.impl.socket.SocketConnector; 8 | import com.ljm.server.io.connector.impl.socket.SocketConnectorFactory; 9 | import com.ljm.server.io.event.listener.impl.ConnectionEventListener; 10 | import com.ljm.server.protocol.http.handler.HttpStaticResourceEventHandler; 11 | import com.ljm.server.protocol.http.parser.*; 12 | 13 | import java.io.IOException; 14 | 15 | /** 16 | * @author 李佳明 https://github.com/pkpk1234 17 | * @date 2018-01-2018/1/7 18 | */ 19 | public class BootStrap { 20 | public static void main(String[] args) throws IOException { 21 | /*EventListener socketEventListener = 22 | new ConnectionEventListener(new EchoEventHandler()); 23 | SocketConnector connector = SocketConnectorFactory.build(18080, socketEventListener); 24 | 25 | EventListener socketEventListener2 = 26 | new ConnectionEventListener(new FileEventHandler(System.getProperty("user.dir"))); 27 | SocketConnector connector2 = 28 | SocketConnectorFactory.build(18081, socketEventListener2);*/ 29 | 30 | EventListener socketEventListener3 = 31 | new ConnectionEventListener(new HttpStaticResourceEventHandler(System.getProperty("user.dir"), 32 | new DefaultHttpRequestMessageParser(new DefaultHttpRequestLineParser(), 33 | new DefaultHttpQueryParameterParser(), 34 | new DefaultHttpHeaderParser(), 35 | new DefaultHttpBodyParser()))); 36 | SocketConnector connector3 = 37 | SocketConnectorFactory.build(18083, socketEventListener3); 38 | 39 | /*EventListener nioEventListener = new SelectableChannleEventListener(new NIOEchoEventHandler()); 40 | SocketChannelConnector socketChannelConnector = new SocketChannelConnector(18082, nioEventListener);*/ 41 | 42 | 43 | ServerConfig serverConfig = ServerConfig.builder() 44 | // .addConnector(connector) 45 | //.addConnector(connector2) 46 | .addConnector(connector3) 47 | //.addConnector(socketChannelConnector) 48 | .build(); 49 | Server server = ServerFactory.getServer(serverConfig); 50 | server.start(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/LifeCycle.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/6 6 | */ 7 | public interface LifeCycle { 8 | /** 9 | * 启动生命周期 10 | */ 11 | void start(); 12 | 13 | /** 14 | * 停止生命周期 15 | */ 16 | void stop(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/Server.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server; 2 | 3 | import com.ljm.server.io.connector.Connector; 4 | 5 | import java.io.IOException; 6 | import java.util.Set; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/4 11 | * server接口 12 | */ 13 | public interface Server { 14 | /** 15 | * 启动服务器 16 | * @throws IOException 17 | */ 18 | void start() throws IOException; 19 | 20 | /** 21 | * 关闭服务器 22 | */ 23 | void stop(); 24 | 25 | /** 26 | * 获取服务器启停状态 27 | * @return 28 | */ 29 | ServerStatus getStatus(); 30 | 31 | /** 32 | * 获取服务器管理的Connector列表 33 | * @return 34 | */ 35 | Set getConnectors(); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/ServerFactory.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server; 2 | 3 | import com.ljm.server.config.ServerConfig; 4 | import com.ljm.server.impl.SimpleServer; 5 | 6 | /** 7 | * @author 李佳明 https://github.com/pkpk1234 8 | * @date 2018-01-2018/1/5 9 | */ 10 | public class ServerFactory { 11 | /** 12 | * 返回Server实例 13 | * 14 | * @return 15 | */ 16 | public static Server getServer(ServerConfig serverConfig) { 17 | return new SimpleServer(serverConfig.getConnectors()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/ServerStatus.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/5 6 | */ 7 | public enum ServerStatus { 8 | //服务器已经启动 9 | STARTED, 10 | //服务器已经停止 11 | STOPED 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/config/ServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.config; 2 | 3 | import com.ljm.server.io.connector.Connector; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/5 11 | */ 12 | public class ServerConfig { 13 | public static final int DEFAULT_PORT = 18080; 14 | private final Set connectors; 15 | 16 | private ServerConfig(Builder builder) { 17 | connectors = builder.connectors; 18 | } 19 | 20 | public Set getConnectors() { 21 | return connectors; 22 | } 23 | 24 | public static Builder builder() { 25 | return new Builder(); 26 | } 27 | 28 | public static final class Builder { 29 | private Set connectors; 30 | 31 | private Builder() { 32 | connectors = new HashSet<>(16); 33 | } 34 | 35 | public Builder withConnectors(Set connectors) { 36 | this.connectors = connectors; 37 | return this; 38 | } 39 | 40 | public Builder addConnector(Connector connector) { 41 | this.connectors.add(connector); 42 | return this; 43 | } 44 | 45 | public ServerConfig build() { 46 | return new ServerConfig(this); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/demo/EchoEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.demo; 2 | 3 | import com.ljm.server.event.handler.AbstractEventHandler; 4 | import com.ljm.server.event.handler.HandlerException; 5 | import com.ljm.server.io.connection.Connection; 6 | import com.ljm.server.io.utils.IoUtils; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | import java.io.PrintWriter; 12 | import java.util.Scanner; 13 | 14 | /** 15 | * @author 李佳明 https://github.com/pkpk1234 16 | * @date 2018-01-2018/1/8 17 | */ 18 | public class EchoEventHandler extends AbstractEventHandler { 19 | 20 | @Override 21 | protected void doHandle(Connection connection) { 22 | try { 23 | echo(connection.getInputStream(), connection.getOutputStream()); 24 | } catch (IOException e) { 25 | throw new HandlerException(e); 26 | } 27 | } 28 | 29 | protected void echo(InputStream inputstream, OutputStream outputStream) { 30 | try { 31 | Scanner scanner = new Scanner(inputstream); 32 | PrintWriter printWriter = new PrintWriter(outputStream); 33 | printWriter.append("Server connected.Welcome to echo.\n"); 34 | printWriter.flush(); 35 | while (scanner.hasNextLine()) { 36 | String line = scanner.nextLine(); 37 | if ("stop".equals(line)) { 38 | printWriter.append("bye bye.\n"); 39 | printWriter.flush(); 40 | break; 41 | } else { 42 | printWriter.append(line); 43 | printWriter.append("\n"); 44 | printWriter.flush(); 45 | } 46 | } 47 | } finally { 48 | IoUtils.closeQuietly(inputstream); 49 | IoUtils.closeQuietly(outputStream); 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/demo/FileEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.demo; 2 | 3 | import com.ljm.server.event.handler.AbstractEventHandler; 4 | import com.ljm.server.event.handler.HandlerException; 5 | import com.ljm.server.io.connection.Connection; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | 11 | /** 12 | * @author 李佳明 https://github.com/pkpk1234 13 | * @date 2018-01-2018/1/8 14 | */ 15 | public class FileEventHandler extends AbstractEventHandler { 16 | 17 | private final String docBase; 18 | private final FileTransfer fileTransfer = new FileTransfer(); 19 | 20 | public FileEventHandler(String docBase) { 21 | this.docBase = docBase; 22 | } 23 | 24 | @Override 25 | protected void doHandle(Connection connection) { 26 | 27 | try { 28 | getFile(this.docBase, connection.getInputStream(), 29 | connection.getOutputStream()); 30 | } catch (IOException e) { 31 | throw new HandlerException(e); 32 | } 33 | } 34 | 35 | /** 36 | * 返回文件 37 | * 38 | * @param inputstream 39 | * @param outputStream 40 | */ 41 | private void getFile(String docBase, InputStream inputstream, 42 | OutputStream outputStream) { 43 | fileTransfer.getFile(docBase, inputstream, outputStream); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/demo/FileTransfer.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.demo; 2 | 3 | import com.ljm.server.event.handler.HandlerException; 4 | import com.ljm.server.io.utils.IoUtils; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.io.PrintWriter; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.Scanner; 14 | 15 | /** 16 | * @author 李佳明 https://github.com/pkpk1234 17 | */ 18 | public class FileTransfer { 19 | public FileTransfer() { 20 | } 21 | 22 | /** 23 | * 返回文件 24 | * 25 | * @param inputstream 26 | * @param outputStream 27 | */ 28 | void getFile(String docBase, InputStream inputstream, OutputStream outputStream) { 29 | try { 30 | Scanner scanner = new Scanner(inputstream, "UTF-8"); 31 | PrintWriter printWriter = new PrintWriter(outputStream); 32 | printWriter.append("Server connected.Welcome to File Server.\n"); 33 | printWriter.flush(); 34 | while (scanner.hasNextLine()) { 35 | String line = scanner.nextLine(); 36 | if ("stop".equals(line)) { 37 | printWriter.append("bye bye.\n"); 38 | printWriter.flush(); 39 | break; 40 | } else { 41 | Path filePath = Paths.get(docBase, line); 42 | //如果是目录,就打印文件列表 43 | if (Files.isDirectory(filePath)) { 44 | printWriter.append("目录 ").append(filePath.toString()) 45 | .append(" 下有文件:").append("\n"); 46 | Files.list(filePath).forEach(fileName -> printWriter.append(fileName.getFileName().toString()) 47 | .append("\n").flush()); 48 | //如果文件可读,就打印文件内容 49 | } else if (Files.isReadable(filePath)) { 50 | printWriter.append("File ").append(filePath.toString()) 51 | .append(" 的内容是:").append("\n").flush(); 52 | Files.copy(filePath, outputStream); 53 | printWriter.append("\n"); 54 | //其他情况返回文件找不到 55 | } else { 56 | printWriter.append("File ").append(filePath.toString()) 57 | .append(" is not found.").append("\n").flush(); 58 | } 59 | 60 | } 61 | } 62 | } catch (IOException e) { 63 | throw new HandlerException(e); 64 | } finally { 65 | IoUtils.closeQuietly(inputstream); 66 | IoUtils.closeQuietly(outputStream); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/demo/NIOEchoEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.demo; 2 | 3 | import com.ljm.server.event.handler.AbstractEventHandler; 4 | import com.ljm.server.event.handler.HandlerException; 5 | import com.ljm.server.io.connection.channel.ChannelHelper; 6 | import com.ljm.server.io.utils.IoUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | import java.nio.ByteBuffer; 12 | import java.nio.CharBuffer; 13 | import java.nio.channels.SelectionKey; 14 | import java.nio.channels.SocketChannel; 15 | import java.nio.charset.Charset; 16 | 17 | /** 18 | * @author 李佳明 https://github.com/pkpk1234 19 | * @date 2018-01-2018/1/10 20 | */ 21 | public class NIOEchoEventHandler extends AbstractEventHandler { 22 | private static final Logger LOGGER = LoggerFactory.getLogger(NIOEchoEventHandler.class); 23 | private static final Charset CHARSET = Charset.forName("utf-8"); 24 | private static final String LINE_SPLITTER = System.getProperty("line.separator"); 25 | 26 | @Override 27 | protected void doHandle(SelectionKey key) { 28 | try { 29 | if (key.isWritable()) { 30 | 31 | ByteBuffer buffer = (ByteBuffer) key.attachment(); 32 | SocketChannel socketChannel = (SocketChannel) key.channel(); 33 | buffer.flip(); 34 | String data = toString(buffer); 35 | if (!data.contains(LINE_SPLITTER)) { 36 | return; 37 | } 38 | String outputData = data.substring(0, 39 | data.indexOf(LINE_SPLITTER) + LINE_SPLITTER.length()); 40 | 41 | ByteBuffer outputBuffer = fromString("echo:" + outputData); 42 | ChannelHelper.doWrite(outputBuffer, socketChannel); 43 | 44 | ByteBuffer temp = fromString(outputData); 45 | buffer.position(temp.limit()); 46 | buffer.compact(); 47 | if (outputData.equals("stop" + LINE_SPLITTER)) { 48 | key.cancel(); 49 | IoUtils.closeQuietly(socketChannel); 50 | } 51 | } else if (key.isReadable()) { 52 | ByteBuffer buffer = (ByteBuffer) key.attachment(); 53 | SocketChannel socketChannel = (SocketChannel) key.channel(); 54 | ByteBuffer readBuff = ByteBuffer.allocate(32); 55 | while (socketChannel.read(buffer) != -1) { 56 | readBuff.flip(); 57 | buffer.limit(buffer.capacity()); 58 | buffer.put(readBuff); 59 | } 60 | 61 | } 62 | } catch (IOException e) { 63 | throw new HandlerException(e); 64 | } 65 | } 66 | 67 | private String toString(ByteBuffer byteBuffer) { 68 | 69 | CharBuffer charBuffer = CHARSET.decode(byteBuffer); 70 | return charBuffer.toString(); 71 | } 72 | 73 | public ByteBuffer fromString(String str) { 74 | return CHARSET.encode(str); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/event/EventException.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.event; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/7 6 | */ 7 | public class EventException extends RuntimeException { 8 | public EventException() { 9 | super(); 10 | } 11 | 12 | public EventException(String message) { 13 | super(message); 14 | } 15 | 16 | public EventException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public EventException(Throwable cause) { 21 | super(cause); 22 | } 23 | 24 | protected EventException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 25 | super(message, cause, enableSuppression, writableStackTrace); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/event/handler/AbstractEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.event.handler; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/8 6 | */ 7 | public abstract class AbstractEventHandler implements EventHandler { 8 | @Override 9 | public void handle(T obj) throws HandlerException { 10 | beforeHandle(obj); 11 | doHandle(obj); 12 | afterHandle(obj); 13 | } 14 | 15 | /** 16 | * 事件处理前置处理方法 17 | * 18 | * @param obj 19 | */ 20 | protected void beforeHandle(T obj) { 21 | } 22 | 23 | /** 24 | * 事件处理方法 25 | * 26 | * @param obj 27 | */ 28 | protected abstract void doHandle(T obj); 29 | 30 | /** 31 | * 事件处理后置处理方法 32 | * 33 | * @param obj 34 | */ 35 | protected void afterHandle(T obj) { 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/event/handler/EventHandler.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.event.handler; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/8 6 | */ 7 | public interface EventHandler { 8 | /** 9 | * 处理事件 10 | * @param obj 11 | * @throws HandlerException 12 | */ 13 | void handle(T obj) throws HandlerException; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/event/handler/HandlerException.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.event.handler; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/8 6 | */ 7 | public class HandlerException extends RuntimeException { 8 | public HandlerException() { 9 | super(); 10 | } 11 | 12 | public HandlerException(String message) { 13 | super(message); 14 | } 15 | 16 | public HandlerException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public HandlerException(Throwable cause) { 21 | super(cause); 22 | } 23 | 24 | protected HandlerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 25 | super(message, cause, enableSuppression, writableStackTrace); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/event/listener/AbstractEventListener.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.event.listener; 2 | 3 | import com.ljm.server.event.EventException; 4 | import com.ljm.server.event.handler.EventHandler; 5 | 6 | /** 7 | * @author 李佳明 https://github.com/pkpk1234 8 | * @date 2018-01-2018/1/8 9 | */ 10 | public abstract class AbstractEventListener implements EventListener { 11 | /** 12 | * 事件处理流程模板方法 13 | * @param event 事件对象 14 | * @throws EventException 15 | */ 16 | @Override 17 | public void onEvent(T event) throws EventException { 18 | EventHandler eventHandler = getEventHandler(event); 19 | eventHandler.handle(event); 20 | } 21 | 22 | /** 23 | * 返回事件处理器 24 | * @param event 25 | * @return 26 | */ 27 | protected abstract EventHandler getEventHandler(T event); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/event/listener/EventListener.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.event.listener; 2 | 3 | import com.ljm.server.event.EventException; 4 | 5 | /** 6 | * @author 李佳明 https://github.com/pkpk1234 7 | * @date 2018-01-2018/1/7 8 | */ 9 | public interface EventListener { 10 | /** 11 | * 事件发生时的回调方法 12 | * @param event 事件对象 13 | * @throws EventException 处理事件时异常都转换为该异常抛出 14 | */ 15 | void onEvent(T event) throws EventException; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/impl/SimpleServer.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.impl; 2 | 3 | import com.ljm.server.LifeCycle; 4 | import com.ljm.server.Server; 5 | import com.ljm.server.ServerStatus; 6 | import com.ljm.server.io.connector.Connector; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.Set; 11 | 12 | /** 13 | * @author 李佳明 https://github.com/pkpk1234 14 | * @date 2018-01-2018/1/4 15 | */ 16 | public class SimpleServer implements Server { 17 | private static final Logger logger = LoggerFactory.getLogger(SimpleServer.class); 18 | private volatile ServerStatus serverStatus = ServerStatus.STOPED; 19 | private final Set connectors; 20 | 21 | public SimpleServer(Set connectorList) { 22 | this.connectors = connectorList; 23 | } 24 | 25 | @Override 26 | public void start() { 27 | connectors.forEach(LifeCycle::start); 28 | this.serverStatus = ServerStatus.STARTED; 29 | } 30 | 31 | @Override 32 | public void stop() { 33 | connectors.forEach(LifeCycle::stop); 34 | this.serverStatus = ServerStatus.STOPED; 35 | logger.info("Server stop"); 36 | } 37 | 38 | @Override 39 | public ServerStatus getStatus() { 40 | return serverStatus; 41 | } 42 | 43 | @Override 44 | public Set getConnectors() { 45 | return connectors; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connection/ChannelConnection.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connection; 2 | 3 | 4 | import java.io.IOException; 5 | import java.nio.ByteBuffer; 6 | 7 | /** 8 | * 底层为Channel的Connection 9 | * @author 李佳明 https://github.com/pkpk1234 10 | */ 11 | public interface ChannelConnection extends Connection { 12 | /** 13 | * 读取数据 14 | * 15 | * @param byteBuffer 16 | * @return 17 | * @throws IOException 18 | */ 19 | byte[] read(ByteBuffer byteBuffer) throws IOException; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connection/Connection.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connection; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | 7 | /** 8 | * @author 李佳明 https://github.com/pkpk1234 9 | * @date 2018-01-2018/1/10 10 | */ 11 | public interface Connection { 12 | /** 13 | * 写入byte[]数组中的所有数据 14 | * 15 | * @param bytes 16 | * @throws IOException 17 | */ 18 | void write(byte[] bytes) throws IOException; 19 | 20 | /** 21 | * 写入byte[]数组中[offset,length-1]的数据 22 | * 23 | * @param bytes 24 | * @param offset 25 | * @param length 26 | * @throws IOException 27 | */ 28 | void write(byte[] bytes, int offset, int length) throws IOException; 29 | 30 | /** 31 | * 读取数据填满byte[]数组 32 | * 33 | * @param bytes 34 | * @return 35 | * @throws IOException 36 | */ 37 | int read(byte[] bytes) throws IOException; 38 | 39 | /** 40 | * 读取数据写入byte[offset,length-1] 41 | * 42 | * @param bytes 43 | * @param offset 44 | * @param length 45 | * @return 46 | * @throws IOException 47 | */ 48 | int read(byte[] bytes, int offset, int length) throws IOException; 49 | 50 | /** 51 | * 获取Socket中的输入流 52 | * @return 53 | * @throws IOException 54 | */ 55 | InputStream getInputStream() throws IOException; 56 | 57 | /** 58 | * 获取Socket中的输出流 59 | * @return 60 | * @throws IOException 61 | */ 62 | OutputStream getOutputStream() throws IOException; 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connection/channel/ChannelHelper.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connection.channel; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | import java.nio.channels.SocketChannel; 6 | import java.util.Objects; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | */ 11 | public class ChannelHelper { 12 | public static void doWrite(final ByteBuffer buffer, final SocketChannel channel) throws IOException { 13 | if (Objects.isNull(buffer) || Objects.isNull(channel)) { 14 | throw new IllegalArgumentException("Required buffer and channel."); 15 | } 16 | while (buffer.hasRemaining()) { 17 | channel.write(buffer); 18 | } 19 | } 20 | 21 | public void doRead(final StringBuilder data, final ByteBuffer buffer, final SocketChannel channel) throws IOException { 22 | while (channel.read(buffer) != -1) { 23 | data.append(new String(buffer.array()).trim()); 24 | buffer.clear(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connection/channel/SelectableChannelConnection.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connection.channel; 2 | 3 | import com.ljm.server.io.connection.ChannelConnection; 4 | import com.ljm.server.io.connector.ConnectorException; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.nio.ByteBuffer; 11 | import java.nio.channels.SelectionKey; 12 | import java.nio.channels.SocketChannel; 13 | 14 | /** 15 | * @author 李佳明 https://github.com/pkpk1234 16 | * @date 2018-01-2018/1/10 17 | */ 18 | public class SelectableChannelConnection implements ChannelConnection { 19 | 20 | private final SelectionKey selectionKey; 21 | 22 | public SelectableChannelConnection(SelectionKey selectionKey) { 23 | this.selectionKey = selectionKey; 24 | } 25 | 26 | 27 | @Override 28 | public void write(byte[] bytes) throws IOException { 29 | this.write(bytes, 0, bytes.length); 30 | } 31 | 32 | @Override 33 | public void write(byte[] bytes, int offset, int length) throws IOException { 34 | if (selectionKey.isWritable()) { 35 | SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); 36 | ByteBuffer output = ByteBuffer.wrap(bytes, 0, bytes.length); 37 | output.flip(); 38 | ChannelHelper.doWrite(output, socketChannel); 39 | } 40 | } 41 | 42 | @Override 43 | public int read(byte[] bytes) { 44 | throw new ConnectorException("not support BIO"); 45 | } 46 | 47 | @Override 48 | public int read(byte[] bytes, int start, int end) { 49 | throw new ConnectorException("not support BIO"); 50 | } 51 | 52 | @Override 53 | public InputStream getInputStream() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public OutputStream getOutputStream() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public byte[] read(ByteBuffer byteBuffer) throws IOException { 64 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(64); 65 | if (selectionKey.isReadable()) { 66 | SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); 67 | ByteBuffer buffer = ByteBuffer.allocate(64); 68 | int readLenght = -1; 69 | byteArrayOutputStream = new ByteArrayOutputStream(64); 70 | while ((readLenght = socketChannel.read(buffer)) != -1) { 71 | buffer.flip(); 72 | byte[] bytes = new byte[buffer.remaining()]; 73 | byteBuffer.clear(); 74 | byteArrayOutputStream.write(byteBuffer.get(bytes).array()); 75 | } 76 | } 77 | return byteArrayOutputStream.toByteArray(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connection/socket/SocketConnection.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connection.socket; 2 | 3 | import com.ljm.server.io.connection.Connection; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | import java.net.Socket; 9 | 10 | /** 11 | * @author 李佳明 https://github.com/pkpk1234 12 | * @date 2018-01-2018/1/10 13 | */ 14 | public class SocketConnection implements Connection { 15 | private final Socket socket; 16 | 17 | public SocketConnection(Socket socket) { 18 | this.socket = socket; 19 | } 20 | 21 | @Override 22 | public void write(byte[] bytes) throws IOException { 23 | this.write(bytes, 0, bytes.length); 24 | } 25 | 26 | @Override 27 | public void write(byte[] bytes, int offset, int length) throws IOException { 28 | 29 | this.socket.getOutputStream().write(bytes, offset, length); 30 | 31 | } 32 | 33 | @Override 34 | public int read(byte[] bytes) throws IOException { 35 | return this.read(bytes, 0, bytes.length); 36 | } 37 | 38 | @Override 39 | public int read(byte[] bytes, int offset, int length) throws IOException { 40 | return this.socket.getInputStream().read(bytes, offset, length); 41 | } 42 | 43 | @Override 44 | public InputStream getInputStream() throws IOException { 45 | return this.socket.getInputStream(); 46 | } 47 | 48 | @Override 49 | public OutputStream getOutputStream() throws IOException { 50 | return this.socket.getOutputStream(); 51 | } 52 | 53 | public Socket getSocket() { 54 | return socket; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connector/AbstractChannelConnector.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connector; 2 | 3 | import com.ljm.server.io.connection.Connection; 4 | 5 | import java.nio.channels.SelectionKey; 6 | 7 | /** 8 | * ChannelConnector抽象类 9 | * @author 李佳明 https://github.com/pkpk1234 10 | */ 11 | public abstract class AbstractChannelConnector extends AbstractConnector { 12 | /** 13 | * 通过SelectableChannel通信 14 | * @param selectionKey 15 | * @throws ConnectorException 16 | */ 17 | protected abstract void communicate(SelectionKey selectionKey) throws ConnectorException; 18 | 19 | @Override 20 | protected void communicate(Connection connection) throws ConnectorException { 21 | throw new ConnectorException("not support BIO."); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connector/AbstractConnector.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connector; 2 | 3 | import com.ljm.server.io.connection.Connection; 4 | 5 | /** 6 | * @author 李佳明 https://github.com/pkpk1234 7 | * @date 2018-01-2018/1/6 8 | */ 9 | public abstract class AbstractConnector implements Connector { 10 | @Override 11 | public void start() { 12 | init(); 13 | acceptConnect(); 14 | } 15 | 16 | /** 17 | * 初始化connector 18 | * 一般为启动端口监听 19 | * @throws ConnectorException 20 | */ 21 | protected abstract void init() throws ConnectorException; 22 | 23 | /** 24 | * 接收请求连接 25 | * @throws ConnectorException 26 | */ 27 | protected abstract void acceptConnect() throws ConnectorException; 28 | 29 | /** 30 | * 使用connection进行通信 31 | * @param connection 32 | * @throws ConnectorException 33 | */ 34 | protected abstract void communicate(Connection connection) throws ConnectorException; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connector/Connector.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connector; 2 | 3 | import com.ljm.server.LifeCycle; 4 | 5 | /** 6 | * @author 李佳明 https://github.com/pkpk1234 7 | * @date 2018-01-2018/1/9 8 | */ 9 | public interface Connector extends LifeCycle { 10 | /** 11 | * 获取监听的端口 12 | * 13 | * @return 14 | */ 15 | int getPort(); 16 | 17 | /** 18 | * 获取监听的IP、主机名 19 | * 20 | * @return 21 | */ 22 | String getHost(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connector/ConnectorException.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connector; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/6 6 | */ 7 | public class ConnectorException extends RuntimeException { 8 | public ConnectorException() { 9 | } 10 | 11 | public ConnectorException(String message) { 12 | super(message); 13 | } 14 | 15 | public ConnectorException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public ConnectorException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | public ConnectorException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connector/ConnectorFactory.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connector; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/7 6 | */ 7 | public interface ConnectorFactory { 8 | /** 9 | * 返回Connector 10 | * @return 11 | */ 12 | AbstractConnector getConnector(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connector/impl/nio/SocketChannelConnector.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connector.impl.nio; 2 | 3 | import com.ljm.server.event.listener.EventListener; 4 | import com.ljm.server.io.connector.AbstractChannelConnector; 5 | import com.ljm.server.io.connector.ConnectorException; 6 | import com.ljm.server.io.utils.IoUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | import java.net.InetAddress; 12 | import java.net.InetSocketAddress; 13 | import java.net.SocketAddress; 14 | import java.nio.ByteBuffer; 15 | import java.nio.channels.SelectionKey; 16 | import java.nio.channels.Selector; 17 | import java.nio.channels.ServerSocketChannel; 18 | import java.nio.channels.SocketChannel; 19 | import java.util.Iterator; 20 | import java.util.Set; 21 | 22 | /** 23 | * @author 李佳明 https://github.com/pkpk1234 24 | * @date 2018-01-2018/1/9 25 | */ 26 | public class SocketChannelConnector extends AbstractChannelConnector { 27 | private static final Logger LOGGER = LoggerFactory.getLogger(SocketChannelConnector.class); 28 | private static final String LOCALHOST = "localhost"; 29 | private static final int DEFAULT_BACKLOG = 50; 30 | private final int port; 31 | private final String host; 32 | private final int backLog; 33 | private ServerSocketChannel serverSocketChannel; 34 | private volatile boolean started = false; 35 | private final EventListener eventListener; 36 | 37 | public SocketChannelConnector(int port, String host, int backLog, EventListener eventListener) { 38 | this.port = port; 39 | this.host = host; 40 | this.backLog = backLog; 41 | this.eventListener = eventListener; 42 | } 43 | 44 | public SocketChannelConnector(int port, EventListener eventHandler) { 45 | this(port, LOCALHOST, DEFAULT_BACKLOG, eventHandler); 46 | } 47 | 48 | @Override 49 | protected void init() throws ConnectorException { 50 | try { 51 | InetAddress inetAddress = InetAddress.getByName(this.host); 52 | SocketAddress socketAddress = new InetSocketAddress(inetAddress, this.port); 53 | this.serverSocketChannel = 54 | ServerSocketChannel.open(); 55 | this.serverSocketChannel.configureBlocking(false); 56 | this.serverSocketChannel.bind(socketAddress, backLog); 57 | this.started = true; 58 | LOGGER.info("NioServer started"); 59 | } catch (IOException e) { 60 | throw new ConnectorException(e); 61 | } 62 | } 63 | 64 | @Override 65 | protected void acceptConnect() throws ConnectorException { 66 | 67 | new Thread(() -> { 68 | try { 69 | Selector selector = Selector.open(); 70 | SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 71 | while (true) { 72 | try { 73 | selector.select(); 74 | Set selectedKeys = selector.selectedKeys(); 75 | Iterator iterator = selectedKeys.iterator(); 76 | while (iterator.hasNext()) { 77 | key = (SelectionKey) iterator.next(); 78 | iterator.remove(); 79 | if (key.isAcceptable()) { 80 | ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 81 | SocketChannel socketChannel = ssc.accept(); 82 | socketChannel.configureBlocking(false); 83 | ByteBuffer buffer = ByteBuffer.allocate(1024); 84 | socketChannel.register(selector, SelectionKey.OP_READ 85 | | SelectionKey.OP_WRITE, buffer); 86 | LOGGER.info("NIO Connector accept Connect from {}", 87 | socketChannel.getRemoteAddress()); 88 | } else if (key.isReadable() || key.isWritable()) { 89 | communicate(key); 90 | } 91 | } 92 | } catch (IOException e) { 93 | LOGGER.error("nio error", e); 94 | if (key != null) { 95 | key.cancel(); 96 | IoUtils.closeQuietly(key.channel()); 97 | } 98 | } 99 | } 100 | } catch (IOException e) { 101 | throw new ConnectorException(e); 102 | } 103 | }).start(); 104 | 105 | } 106 | 107 | @Override 108 | protected void communicate(SelectionKey selectionKey) throws ConnectorException { 109 | this.eventListener.onEvent(selectionKey); 110 | } 111 | 112 | @Override 113 | public int getPort() { 114 | return this.port; 115 | } 116 | 117 | @Override 118 | public String getHost() { 119 | return this.host; 120 | } 121 | 122 | @Override 123 | public void stop() { 124 | this.started = false; 125 | IoUtils.closeQuietly(this.serverSocketChannel); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connector/impl/socket/SocketConnector.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connector.impl.socket; 2 | 3 | import com.ljm.server.event.listener.EventListener; 4 | import com.ljm.server.io.connection.Connection; 5 | import com.ljm.server.io.connection.socket.SocketConnection; 6 | import com.ljm.server.io.connector.AbstractConnector; 7 | import com.ljm.server.io.connector.ConnectorException; 8 | import com.ljm.server.io.utils.IoUtils; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.IOException; 14 | import java.net.InetAddress; 15 | import java.net.ServerSocket; 16 | import java.net.Socket; 17 | 18 | /** 19 | * @author 李佳明 https://github.com/pkpk1234 20 | * @date 2018-01-2018/1/6 21 | */ 22 | public class SocketConnector extends AbstractConnector { 23 | private static final Logger LOGGER = LoggerFactory.getLogger(SocketConnector.class); 24 | private static final String LOCALHOST = "localhost"; 25 | private static final int DEFAULT_BACKLOG = 50; 26 | private final int port; 27 | private final String host; 28 | private final int backLog; 29 | private ServerSocket serverSocket; 30 | private volatile boolean started = false; 31 | private final EventListener eventListener; 32 | 33 | public SocketConnector(int port, EventListener eventListener) { 34 | this(port, LOCALHOST, DEFAULT_BACKLOG, eventListener); 35 | } 36 | 37 | public SocketConnector(int port, String host, int backLog, EventListener eventListener) { 38 | this.port = port; 39 | this.host = StringUtils.isBlank(host) ? LOCALHOST : host; 40 | this.backLog = backLog; 41 | this.eventListener = eventListener; 42 | } 43 | 44 | 45 | @Override 46 | protected void init() throws ConnectorException { 47 | 48 | //监听本地端口,如果监听不成功,抛出异常 49 | try { 50 | InetAddress inetAddress = InetAddress.getByName(this.host); 51 | this.serverSocket = new ServerSocket(this.port, backLog, inetAddress); 52 | this.started = true; 53 | } catch (IOException e) { 54 | throw new ConnectorException(e); 55 | } 56 | } 57 | 58 | @Override 59 | protected void acceptConnect() throws ConnectorException { 60 | new Thread(() -> { 61 | while (started) { 62 | Socket socket = null; 63 | try { 64 | socket = serverSocket.accept(); 65 | communicate(new SocketConnection(socket)); 66 | } catch (Exception e) { 67 | //单个Socket异常,不要影响整个Connector 68 | LOGGER.error(e.getMessage(), e); 69 | } finally { 70 | IoUtils.closeQuietly(socket); 71 | } 72 | } 73 | }).start(); 74 | } 75 | 76 | @Override 77 | protected void communicate(Connection connection) throws ConnectorException { 78 | this.eventListener.onEvent(connection); 79 | } 80 | 81 | @Override 82 | public void stop() { 83 | this.started = false; 84 | IoUtils.closeQuietly(this.serverSocket); 85 | } 86 | 87 | @Override 88 | public int getPort() { 89 | return this.port; 90 | } 91 | 92 | @Override 93 | public String getHost() { 94 | return this.host; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connector/impl/socket/SocketConnectorConfig.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connector.impl.socket; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/7 6 | */ 7 | public class SocketConnectorConfig { 8 | private final int port; 9 | private final String host; 10 | private final int backLog; 11 | 12 | public SocketConnectorConfig(int port, String host, int backLog) { 13 | this.port = port; 14 | this.host = host; 15 | this.backLog = backLog; 16 | } 17 | 18 | public int getBackLog() { 19 | return backLog; 20 | } 21 | 22 | public SocketConnectorConfig(int port, String host) { 23 | this(port, host, 64); 24 | } 25 | 26 | public SocketConnectorConfig(int port) { 27 | this(port, null); 28 | } 29 | 30 | public int getPort() { 31 | return port; 32 | } 33 | 34 | public String getHost() { 35 | return host; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/connector/impl/socket/SocketConnectorFactory.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connector.impl.socket; 2 | 3 | import com.ljm.server.event.listener.EventListener; 4 | import com.ljm.server.io.connection.Connection; 5 | 6 | /** 7 | * @author 李佳明 https://github.com/pkpk1234 8 | * @date 2018-01-2018/1/7 9 | */ 10 | public class SocketConnectorFactory { 11 | 12 | public static SocketConnector build(SocketConnectorConfig socketConnectorConfig, EventListener eventListener) { 13 | return new SocketConnector(socketConnectorConfig.getPort(), 14 | socketConnectorConfig.getHost(), socketConnectorConfig.getBackLog(), eventListener); 15 | } 16 | 17 | public static SocketConnector build(int port, EventListener eventListener) { 18 | return new SocketConnector(port, eventListener); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/event/listener/impl/ConnectionEventListener.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.event.listener.impl; 2 | 3 | import com.ljm.server.event.handler.EventHandler; 4 | import com.ljm.server.event.listener.AbstractEventListener; 5 | import com.ljm.server.io.connection.Connection; 6 | 7 | /** 8 | * @author 李佳明 https://github.com/pkpk1234 9 | * @date 2018-01-2018/1/7 10 | */ 11 | public class ConnectionEventListener extends AbstractEventListener { 12 | 13 | private final EventHandler eventHandler; 14 | 15 | public ConnectionEventListener(EventHandler eventHandler) { 16 | this.eventHandler = eventHandler; 17 | } 18 | 19 | @Override 20 | protected EventHandler getEventHandler(Connection event) { 21 | return this.eventHandler; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/event/listener/impl/SelectableChannleEventListener.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.event.listener.impl; 2 | 3 | import com.ljm.server.event.handler.EventHandler; 4 | import com.ljm.server.event.listener.AbstractEventListener; 5 | 6 | import java.nio.channels.SelectionKey; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | */ 11 | public class SelectableChannleEventListener extends AbstractEventListener { 12 | private final EventHandler eventHandler; 13 | 14 | public SelectableChannleEventListener(EventHandler eventHandler) { 15 | this.eventHandler = eventHandler; 16 | } 17 | 18 | @Override 19 | protected EventHandler getEventHandler(SelectionKey event) { 20 | return this.eventHandler; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/io/utils/IoUtils.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.utils; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.Closeable; 7 | import java.io.IOException; 8 | 9 | /** 10 | * @author 李佳明 https://github.com/pkpk1234 11 | * @date 2018-01-2018/1/5 12 | */ 13 | public class IoUtils { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(IoUtils.class); 16 | 17 | /** 18 | * 安静地关闭,不抛出异常 19 | * @param closeable 20 | */ 21 | public static void closeQuietly(Closeable closeable) { 22 | if(closeable != null) { 23 | try { 24 | closeable.close(); 25 | } catch (IOException e) { 26 | logger.error(e.getMessage(),e); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/Protocol.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/12 6 | * 7 | * 应用协议接口 8 | */ 9 | public interface Protocol { 10 | /** 11 | * 返回协议名 12 | * @return 13 | */ 14 | String getName(); 15 | 16 | /** 17 | * 返回协议版本 18 | * @return 19 | */ 20 | String version(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/ContentTypeUtil.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.IOException; 8 | import java.util.Properties; 9 | 10 | /** 11 | * Content Type工具类 12 | * 13 | * @author 李佳明 https://github.com/pkpk1234 14 | * @date 2018/1/24 15 | */ 16 | public class ContentTypeUtil { 17 | private static final Logger LOGGER = LoggerFactory.getLogger(ContentTypeUtil.class); 18 | private static Properties properties; 19 | 20 | private ContentTypeUtil() { 21 | } 22 | 23 | static { 24 | properties 25 | = new Properties(); 26 | try { 27 | properties.load(ContentTypeUtil.class 28 | .getClassLoader().getResourceAsStream("mime-mapping.properties")); 29 | } catch (IOException e) { 30 | LOGGER.error("load mime-mapping.properties failed", e); 31 | } 32 | } 33 | 34 | /** 35 | * 通过文件后缀获取到Content-Type 36 | * 37 | * @param filePrefix 38 | * @return 39 | */ 40 | public static String getCotentType(String filePrefix) { 41 | return properties.getProperty(filePrefix, "application/octet-stream"); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/HttpConstants.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | */ 6 | public interface HttpConstants { 7 | String KV_SPLITTER = "="; 8 | String CRLF = "\r\n"; 9 | String CONTENT_TYPE = "Content-Type"; 10 | String CONTENT_LENGTH = "Content-Length"; 11 | String CONTENT_ENCODING = "Content-Encoding"; 12 | String TRANSFER_ENCODING = "Transfer-Encoding"; 13 | String ENCODING_CHUNKED = "chunked"; 14 | String ENCODING_IDENTITY = "identity"; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/HttpMessage.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | import com.ljm.server.protocol.http.body.HttpBody; 4 | import com.ljm.server.protocol.http.header.IMessageHeaders; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/12 11 | *

12 | * HTTP消息 13 | * 参考 https://tools.ietf.org/html/rfc2616#page-31 14 | *

15 | * generic-message = start-line 16 | * *(message-header CRLF) 17 | * CRLF 18 | * [ message-body ] 19 | *

20 | *

21 | * start-line = Request-Line | Status-Line 22 | */ 23 | public interface HttpMessage { 24 | /** 25 | * 获取起始行 26 | * 27 | * @return 28 | */ 29 | StartLine getStartLine(); 30 | 31 | /** 32 | * 获取HTTP头集合 33 | * 34 | * @return 35 | */ 36 | IMessageHeaders getMessageHeaders(); 37 | 38 | /** 39 | * 获取HTTP Body 40 | * 41 | * @return 42 | */ 43 | Optional getHttpBody(); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/HttpProtocol.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | import com.ljm.server.protocol.Protocol; 4 | 5 | /** 6 | * @author 李佳明 https://github.com/pkpk1234 7 | * @date 2018-01-2018/1/13 8 | */ 9 | public class HttpProtocol implements Protocol { 10 | private final String name = "http"; 11 | private final String version; 12 | 13 | private HttpProtocol(String version) { 14 | this.version = version; 15 | } 16 | 17 | public static final HttpProtocol VERSION11 = new HttpProtocol("1.1"); 18 | public static final HttpProtocol VERSION20 = new HttpProtocol("2.0"); 19 | 20 | @Override 21 | public String getName() { 22 | return this.name; 23 | } 24 | 25 | @Override 26 | public String version() { 27 | return this.version; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return this.name + "/" + this.version; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/HttpQueryParameter.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/15 6 | */ 7 | public class HttpQueryParameter { 8 | private final String name; 9 | private final String value; 10 | 11 | public HttpQueryParameter(String name, String value) { 12 | this.name = name; 13 | this.value = value; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public String getValue() { 21 | return value; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "HttpQueryParameter{" + 27 | "name='" + name + '\'' + 28 | ", value='" + value + '\'' + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/HttpQueryParameters.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | import com.google.common.collect.LinkedListMultimap; 4 | 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/15 11 | */ 12 | public class HttpQueryParameters { 13 | 14 | private final LinkedListMultimap prametersMultiMap; 15 | 16 | public HttpQueryParameters() { 17 | this.prametersMultiMap = LinkedListMultimap.create(); 18 | } 19 | 20 | 21 | public List getQueryParameter(String name) { 22 | return this.prametersMultiMap.get(name); 23 | } 24 | 25 | public HttpQueryParameter getFirstQueryParameter(String name) { 26 | if (this.prametersMultiMap.containsKey(name)) { 27 | return this.prametersMultiMap.get(name).get(0); 28 | } 29 | return null; 30 | } 31 | 32 | public List getAllQueryParameters() { 33 | return this.prametersMultiMap.values(); 34 | } 35 | 36 | public void addQueryParameter(HttpQueryParameter httpQueryParameter) { 37 | this.prametersMultiMap.put(httpQueryParameter.getName(), httpQueryParameter); 38 | } 39 | 40 | public void removeQueryParameter(HttpQueryParameter httpQueryParameter) { 41 | this.prametersMultiMap.remove(httpQueryParameter.getName(), httpQueryParameter); 42 | } 43 | 44 | public void removeQueryParameter(String httpQueryParameter) { 45 | this.prametersMultiMap.removeAll(httpQueryParameter); 46 | } 47 | 48 | public boolean hasRequestParameter(String httpQueryParameter) { 49 | return this.prametersMultiMap.containsKey(httpQueryParameter); 50 | } 51 | 52 | public Set getQueryParameterNames() { 53 | return this.prametersMultiMap.keySet(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/HttpRequestMessage.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | import com.ljm.server.protocol.http.body.HttpBody; 4 | import com.ljm.server.protocol.http.header.IMessageHeaders; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/12 11 | *

12 | * HTTP请求 13 | */ 14 | public class HttpRequestMessage implements HttpMessage { 15 | private final RequestLine requestLine; 16 | private final IMessageHeaders messageHeaders; 17 | private final Optional httpBody; 18 | private final HttpQueryParameters httpQueryParameters; 19 | 20 | public HttpRequestMessage(RequestLine requestLine, IMessageHeaders messageHeaders, Optional httpBody, 21 | HttpQueryParameters httpQueryParameters) { 22 | this.requestLine = requestLine; 23 | this.messageHeaders = messageHeaders; 24 | this.httpBody = httpBody; 25 | this.httpQueryParameters = httpQueryParameters; 26 | } 27 | 28 | @Override 29 | public StartLine getStartLine() { 30 | return StartLine.class.cast(this.requestLine); 31 | } 32 | 33 | @Override 34 | public IMessageHeaders getMessageHeaders() { 35 | return this.messageHeaders; 36 | } 37 | 38 | @Override 39 | public Optional getHttpBody() { 40 | return this.httpBody; 41 | } 42 | 43 | public RequestLine getRequestLine() { 44 | return requestLine; 45 | } 46 | 47 | public HttpQueryParameters getHttpQueryParameters() { 48 | return httpQueryParameters; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/HttpResponseMessage.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | 4 | import com.ljm.server.protocol.http.body.HttpBody; 5 | import com.ljm.server.protocol.http.header.IMessageHeaders; 6 | 7 | import java.util.Optional; 8 | 9 | /** 10 | * @author 李佳明 https://github.com/pkpk1234 11 | * @date 2018-01-2018/1/12 12 | */ 13 | public class HttpResponseMessage implements HttpMessage { 14 | private final ResponseLine responseLine; 15 | private final IMessageHeaders messageHeaders; 16 | private final Optional httpBody; 17 | 18 | public HttpResponseMessage(ResponseLine responseLine, 19 | IMessageHeaders messageHeaders, Optional httpBody) { 20 | this.responseLine = responseLine; 21 | this.messageHeaders = messageHeaders; 22 | this.httpBody = httpBody; 23 | } 24 | 25 | @Override 26 | public StartLine getStartLine() { 27 | return StartLine.class.cast(this.responseLine); 28 | } 29 | 30 | @Override 31 | public IMessageHeaders getMessageHeaders() { 32 | return this.messageHeaders; 33 | } 34 | 35 | @Override 36 | public Optional getHttpBody() { 37 | return this.httpBody; 38 | } 39 | 40 | public ResponseLine getResponseLine() { 41 | return responseLine; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/RequestLine.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | import java.net.URI; 4 | 5 | /** 6 | * @author 李佳明 https://github.com/pkpk1234 7 | * @date 2018-01-2018/1/13 8 | *

9 | * HTTP Request 请求行 10 | * 参考https://tools.ietf.org/html/rfc2616#page-31 11 | * Request-Line = Method SP Request-URI SP HTTP-Version CRLF 12 | */ 13 | public class RequestLine implements StartLine { 14 | private final String method; 15 | private final URI requestURI; 16 | private final String httpVersion; 17 | 18 | public RequestLine(String method, URI requestURI, String httpVersion) { 19 | this.method = method; 20 | this.requestURI = requestURI; 21 | this.httpVersion = httpVersion; 22 | } 23 | 24 | public String getMethod() { 25 | return method; 26 | } 27 | 28 | public URI getRequestURI() { 29 | return requestURI; 30 | } 31 | 32 | @Override 33 | public String getHttpVersion() { 34 | return httpVersion; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/ResponseLine.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/13 6 | *

7 | * 参考 https://tools.ietf.org/html/rfc2616#page-31 Status-Line = HTTP-Version SP 8 | * Status-Code SP Reason-Phrase CRLF 9 | */ 10 | public class ResponseLine implements StartLine { 11 | private final String httpVersion; 12 | private final int statusCode; 13 | private final String reason; 14 | 15 | public ResponseLine(String httpVersion, int statusCode, String reason) { 16 | this.httpVersion = httpVersion; 17 | this.statusCode = statusCode; 18 | this.reason = reason; 19 | } 20 | 21 | public int getStatusCode() { 22 | return statusCode; 23 | } 24 | 25 | public String getReason() { 26 | return reason; 27 | } 28 | 29 | public String asString() { 30 | StringBuffer stringBuffer = new StringBuffer(); 31 | stringBuffer.append(this.httpVersion).append(" ").append(this.statusCode) 32 | .append(" ").append(this.reason); 33 | return stringBuffer.toString(); 34 | } 35 | 36 | @Override 37 | public String getHttpVersion() { 38 | return httpVersion; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/StartLine.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/13 6 | */ 7 | public interface StartLine { 8 | /** 9 | * 获取HTTP 版本号 10 | * 11 | * @return 12 | */ 13 | String getHttpVersion(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/body/HttpBody.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.body; 2 | 3 | import com.ljm.server.protocol.http.HttpConstants; 4 | 5 | import java.io.InputStream; 6 | import java.io.UnsupportedEncodingException; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/13 11 | */ 12 | public class HttpBody { 13 | private String contentType; 14 | //压缩格式:deflate、gzip... 15 | private String encoding; 16 | private String transferEncoding; 17 | //编码格式:utf-8、gb2312... 18 | private String charSet; 19 | private HttpBodyInputStream inputStream; 20 | //Body段长度 21 | private long contentLength; 22 | 23 | public HttpBody(InputStream inputStream, String transferEncoding) { 24 | this.transferEncoding = transferEncoding; 25 | if (transferEncoding.equals(HttpConstants.ENCODING_CHUNKED)) { 26 | this.inputStream = new HttpBodyInputStream(inputStream, true); 27 | } else { 28 | this.inputStream = new HttpBodyInputStream(inputStream, false); 29 | } 30 | 31 | } 32 | 33 | public String getContentType() { 34 | return contentType; 35 | } 36 | 37 | public void setContentType(String contentType) { 38 | this.contentType = contentType; 39 | } 40 | 41 | public String getEncoding() { 42 | return encoding; 43 | } 44 | 45 | public void setEncoding(String encoding) { 46 | this.encoding = encoding; 47 | } 48 | 49 | public String getCharSet() { 50 | return charSet; 51 | } 52 | 53 | public void setCharSet(String charSet) { 54 | this.charSet = charSet; 55 | } 56 | 57 | public InputStream getInputStream() { 58 | return inputStream; 59 | } 60 | 61 | 62 | public long getContentLength() { 63 | return contentLength; 64 | } 65 | 66 | public void setContentLength(long contentLength) { 67 | this.inputStream.setContentLength(contentLength); 68 | this.contentLength = contentLength; 69 | } 70 | 71 | public String getTransferEncoding() { 72 | return transferEncoding; 73 | } 74 | 75 | public void setTransferEncoding(String transferEncoding) { 76 | this.transferEncoding = transferEncoding; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/body/HttpBodyInputStream.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.body; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | /** 7 | * 封装Body流 8 | * 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/30 11 | */ 12 | public class HttpBodyInputStream extends InputStream { 13 | //chunked body使用 0\r\n\r\n结尾 14 | private static final char[] TAILER_CHARS = {'0', '\r', '\n', '\r', '\n'}; 15 | private final InputStream inputStream; 16 | private long contentLength; 17 | private boolean isChuncked; 18 | private long readed = 0; 19 | private boolean isFinished = false; 20 | private int idx = 0; 21 | 22 | public HttpBodyInputStream(InputStream inputStream, boolean ifChuncked) { 23 | this.inputStream = inputStream; 24 | this.isChuncked = ifChuncked; 25 | } 26 | 27 | @Override 28 | public int read() throws IOException { 29 | if (isChuncked) { 30 | return readChunked(); 31 | } else { 32 | return readByte(); 33 | } 34 | } 35 | 36 | /** 37 | * 读取chunked body 38 | * 39 | * @return 40 | * @throws IOException 41 | */ 42 | private int readChunked() throws IOException { 43 | if (isFinished) { 44 | return -1; 45 | } 46 | int i = this.inputStream.read(); 47 | if (i == -1) { 48 | return i; 49 | } else { 50 | if (idx == TAILER_CHARS.length - 1) { 51 | isFinished = true; 52 | } else if (TAILER_CHARS[idx++] != (char) i) { 53 | idx = 0; 54 | } 55 | } 56 | return i; 57 | } 58 | 59 | /** 60 | * 读取非chunked body 61 | * 62 | * @return 63 | * @throws IOException 64 | */ 65 | private int readByte() throws IOException { 66 | readed++; 67 | if (readed > contentLength) { 68 | return -1; 69 | } else { 70 | return this.inputStream.read(); 71 | } 72 | } 73 | 74 | public long getContentLength() { 75 | return contentLength; 76 | } 77 | 78 | public void setContentLength(long contentLength) { 79 | this.contentLength = contentLength; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/handler/AbstractHttpEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.handler; 2 | 3 | import com.ljm.server.event.handler.AbstractEventHandler; 4 | import com.ljm.server.event.handler.HandlerException; 5 | import com.ljm.server.io.connection.Connection; 6 | import com.ljm.server.io.connection.socket.SocketConnection; 7 | import com.ljm.server.protocol.http.HttpRequestMessage; 8 | import com.ljm.server.protocol.http.HttpResponseMessage; 9 | import org.apache.commons.io.IOUtils; 10 | 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author 李佳明 https://github.com/pkpk1234 15 | * @date 2018-01-2018/1/22 16 | */ 17 | public abstract class AbstractHttpEventHandler extends AbstractEventHandler { 18 | @Override 19 | protected void doHandle(Connection connection) { 20 | //从输入中构造出HTTP请求对象,Body的内容是延迟读取 21 | HttpRequestMessage requestMessage = doParserRequestMessage(connection); 22 | //构造HTTP响应对象 23 | HttpResponseMessage responseMessage = doGenerateResponseMessage(requestMessage); 24 | try { 25 | //输出响应到客户端 26 | doTransferToClient(responseMessage, connection); 27 | } catch (IOException e) { 28 | throw new HandlerException(e); 29 | } finally { 30 | //完成响应后,关闭Socket 31 | if (connection instanceof SocketConnection) { 32 | IOUtils.closeQuietly(((SocketConnection) connection).getSocket()); 33 | } 34 | } 35 | 36 | } 37 | 38 | /** 39 | * 通过输入构造HttpRequestMessage 40 | * 41 | * @param connection 42 | * @return 43 | */ 44 | protected abstract HttpRequestMessage doParserRequestMessage(Connection connection); 45 | 46 | /** 47 | * 根据HttpRequestMessage生成HttpResponseMessage 48 | * 49 | * @param httpRequestMessage 50 | * @return 51 | */ 52 | protected abstract HttpResponseMessage doGenerateResponseMessage( 53 | HttpRequestMessage httpRequestMessage); 54 | 55 | /** 56 | * 写入HttpResponseMessage到客户端 57 | * 58 | * @param responseMessage 59 | * @param connection 60 | * @throws IOException 61 | */ 62 | protected abstract void doTransferToClient(HttpResponseMessage responseMessage, 63 | Connection connection) throws IOException; 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/handler/HttpStaticResourceEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.handler; 2 | 3 | import com.ljm.server.event.handler.HandlerException; 4 | import com.ljm.server.io.connection.Connection; 5 | import com.ljm.server.protocol.http.*; 6 | import com.ljm.server.protocol.http.body.HttpBody; 7 | import com.ljm.server.protocol.http.header.HttpHeader; 8 | import com.ljm.server.protocol.http.header.HttpMessageHeaders; 9 | import com.ljm.server.protocol.http.parser.AbstractHttpRequestMessageParser; 10 | import com.ljm.server.protocol.http.response.HttpResponseConstants; 11 | import com.ljm.server.protocol.http.response.HttpResponseMessageWriter; 12 | import com.ljm.server.protocol.http.response.ResponseLineConstants; 13 | 14 | import javax.activation.MimetypesFileTypeMap; 15 | import java.io.File; 16 | import java.io.FileInputStream; 17 | import java.io.FileNotFoundException; 18 | import java.io.IOException; 19 | import java.nio.file.Files; 20 | import java.nio.file.Path; 21 | import java.nio.file.Paths; 22 | import java.util.Optional; 23 | 24 | /** 25 | * @author 李佳明 https://github.com/pkpk1234 26 | * @date 2018-01-2018/1/8 27 | */ 28 | public class HttpStaticResourceEventHandler extends AbstractHttpEventHandler { 29 | 30 | private final String docBase; 31 | private final AbstractHttpRequestMessageParser httpRequestMessageParser; 32 | 33 | public HttpStaticResourceEventHandler(String docBase, 34 | AbstractHttpRequestMessageParser httpRequestMessageParser) { 35 | this.docBase = docBase; 36 | this.httpRequestMessageParser = httpRequestMessageParser; 37 | } 38 | 39 | @Override 40 | protected HttpRequestMessage doParserRequestMessage(Connection connection) { 41 | try { 42 | HttpRequestMessage httpRequestMessage = httpRequestMessageParser 43 | .parse(connection.getInputStream()); 44 | return httpRequestMessage; 45 | } catch (IOException e) { 46 | throw new HandlerException(e); 47 | } 48 | } 49 | 50 | @Override 51 | protected HttpResponseMessage doGenerateResponseMessage( 52 | HttpRequestMessage httpRequestMessage) { 53 | String path = httpRequestMessage.getRequestLine().getRequestURI().getPath(); 54 | Path filePath = Paths.get(docBase, path); 55 | //目录、无法读取的文件都返回404 56 | if (Files.isDirectory(filePath) || !Files.isReadable(filePath)) { 57 | return HttpResponseConstants.HTTP_404; 58 | } else { 59 | ResponseLine ok = ResponseLineConstants.RES_200; 60 | HttpMessageHeaders headers = HttpMessageHeaders.newBuilder() 61 | .addHeader("status", "200").build(); 62 | HttpBody httpBody = null; 63 | try { 64 | setContentType(filePath, headers); 65 | File file = filePath.toFile(); 66 | httpBody = new HttpBody(new FileInputStream(file), HttpConstants.ENCODING_IDENTITY); 67 | httpBody.setContentLength(file.length()); 68 | } catch (FileNotFoundException e) { 69 | return HttpResponseConstants.HTTP_404; 70 | } 71 | HttpResponseMessage httpResponseMessage = new HttpResponseMessage(ok, headers, 72 | Optional.ofNullable(httpBody)); 73 | return httpResponseMessage; 74 | } 75 | 76 | } 77 | 78 | /** 79 | * 根据文件后缀设置文件Content-Type 80 | * 81 | * @param filePath 82 | * @param headers 83 | * @throws IOException 84 | */ 85 | private void setContentType(Path filePath, HttpMessageHeaders headers) { 86 | String fileName = filePath.toFile().getName(); 87 | if (fileName.contains(".")) { 88 | int idx = fileName.lastIndexOf("."); 89 | fileName = fileName.substring(idx); 90 | } 91 | String contentType = ContentTypeUtil.getCotentType(fileName); 92 | headers.addHeader(new HttpHeader("Content-Type", contentType)); 93 | } 94 | 95 | @Override 96 | protected void doTransferToClient(HttpResponseMessage responseMessage, 97 | Connection connection) throws IOException { 98 | HttpResponseMessageWriter httpResponseMessageWriter = new HttpResponseMessageWriter(); 99 | httpResponseMessageWriter.write(responseMessage, connection); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/header/HttpHeader.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.header; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/13 6 | *

7 | * HTTP 头 8 | */ 9 | public class HttpHeader { 10 | private final String name; 11 | private final String value; 12 | 13 | public HttpHeader(String name, String value) { 14 | this.name = name; 15 | this.value = value; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public String getValue() { 23 | return value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/header/HttpMessageHeaders.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.header; 2 | 3 | import com.google.common.collect.LinkedListMultimap; 4 | 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/13 11 | */ 12 | public class HttpMessageHeaders implements IMessageHeaders { 13 | 14 | private final LinkedListMultimap headersMultiMap; 15 | 16 | public HttpMessageHeaders() { 17 | this.headersMultiMap = LinkedListMultimap.create(); 18 | } 19 | 20 | private HttpMessageHeaders(Builder builder) { 21 | headersMultiMap = builder.headersMultiMap; 22 | } 23 | 24 | public static Builder newBuilder() { 25 | return new Builder(); 26 | } 27 | 28 | @Override 29 | public List getHeaders(String headerName) { 30 | return this.headersMultiMap.get(headerName); 31 | } 32 | 33 | @Override 34 | public HttpHeader getFirstHeader(String headerName) { 35 | List headers = this.headersMultiMap.get(headerName); 36 | if (headers.isEmpty()) { 37 | return null; 38 | } 39 | else { 40 | return headers.get(0); 41 | } 42 | } 43 | 44 | @Override 45 | public List getAllHeaders() { 46 | return this.headersMultiMap.values(); 47 | } 48 | 49 | @Override 50 | public void addHeader(HttpHeader httpHeader) { 51 | this.headersMultiMap.put(httpHeader.getName(), httpHeader); 52 | } 53 | 54 | @Override 55 | public void removeHeader(HttpHeader httpHeader) { 56 | this.headersMultiMap.remove(httpHeader.getName(), httpHeader); 57 | } 58 | 59 | @Override 60 | public void removeHeaders(String headerName) { 61 | this.headersMultiMap.removeAll(headerName); 62 | } 63 | 64 | @Override 65 | public boolean hasHeader(String headerName) { 66 | return this.headersMultiMap.containsKey(headerName); 67 | } 68 | 69 | @Override 70 | public Set getHeaderNames() { 71 | return this.headersMultiMap.keySet(); 72 | } 73 | 74 | @Override 75 | public String asString() { 76 | StringBuffer stringBuffer = new StringBuffer(); 77 | for (HttpHeader header : this.headersMultiMap.values()) { 78 | stringBuffer.append(header.getName()).append(":").append(header.getValue()) 79 | .append("\r\n"); 80 | } 81 | return stringBuffer.toString(); 82 | } 83 | 84 | public static final class Builder { 85 | private final LinkedListMultimap headersMultiMap; 86 | 87 | private Builder() { 88 | this.headersMultiMap = LinkedListMultimap.create(); 89 | } 90 | 91 | public Builder addHeader(String name, String value) { 92 | this.headersMultiMap.put(name, new HttpHeader(name, value)); 93 | return this; 94 | } 95 | 96 | public HttpMessageHeaders build() { 97 | return new HttpMessageHeaders(this); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/header/IMessageHeaders.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.header; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | /** 7 | * @author 李佳明 https://github.com/pkpk1234 8 | * @date 2018-01-2018/1/13 9 | */ 10 | public interface IMessageHeaders { 11 | 12 | /** 13 | * 获取名字为headerName的HttpHeader列表 14 | * 15 | * @param headerName 16 | * @return 17 | */ 18 | List getHeaders(String headerName); 19 | 20 | /** 21 | * 获取第一个名字为headerName的HttpHeader 22 | * 23 | * @param headerName 24 | * @return 25 | */ 26 | HttpHeader getFirstHeader(String headerName); 27 | 28 | /** 29 | * 获取所有的HttpHeader 30 | * 31 | * @return 32 | */ 33 | List getAllHeaders(); 34 | 35 | /** 36 | * 添加HttpHeader到集合中 37 | * 38 | * @param httpHeader 39 | */ 40 | void addHeader(HttpHeader httpHeader); 41 | 42 | /** 43 | * 从集合中移除HttpHeader 44 | * 45 | * @param httpHeader 46 | */ 47 | void removeHeader(HttpHeader httpHeader); 48 | 49 | /** 50 | * 从集合中移除名字为headerName的所有HttpHeader 51 | * 52 | * @param headerName 53 | */ 54 | void removeHeaders(String headerName); 55 | 56 | /** 57 | * 判断集合中是否包含名字为headerName的HttpHeader 58 | * 59 | * @param headerName 60 | * @return 61 | */ 62 | boolean hasHeader(String headerName); 63 | 64 | /** 65 | * 返回所有HttpHeader的名字,并去重 66 | * 67 | * @return 68 | */ 69 | Set getHeaderNames(); 70 | 71 | /** 72 | * 将Headers集合中的键值对转换为HTTP协议中规定的字符串格式: 73 | * key:value CRLF 74 | * ... 75 | * 76 | * @return 77 | */ 78 | String asString(); 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/AbstractHttpRequestMessageParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.HttpQueryParameters; 4 | import com.ljm.server.protocol.http.HttpRequestMessage; 5 | import com.ljm.server.protocol.http.RequestLine; 6 | import com.ljm.server.protocol.http.body.HttpBody; 7 | import com.ljm.server.protocol.http.header.IMessageHeaders; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.util.Optional; 15 | 16 | /** 17 | * @author 李佳明 https://github.com/pkpk1234 18 | * @date 2018-01-2018/1/14 19 | */ 20 | public abstract class AbstractHttpRequestMessageParser implements HttpRequestMessageParser { 21 | private static final Logger LOGGER = LoggerFactory.getLogger(AbstractHttpRequestMessageParser.class); 22 | 23 | /** 24 | * 定义parse流程 25 | * 26 | * @return 27 | */ 28 | @Override 29 | public HttpRequestMessage parse(InputStream inputStream) throws IOException { 30 | //1.设置上下文:设置是否有body、body之前byte数组,以及body之前byte数组长度到上下文中 31 | getAndSetBytesBeforeBodyToContext(inputStream); 32 | //2.解析构造RequestLine 33 | RequestLine requestLine = parseRequestLine(); 34 | //3.解析构造QueryParameters 35 | HttpQueryParameters httpQueryParameters = parseHttpQueryParameters(); 36 | //4.解析构造HTTP请求头 37 | IMessageHeaders messageHeaders = parseRequestHeaders(); 38 | //5.解析构造HTTP Body,如果有个的话 39 | Optional httpBody = parseRequestBody(); 40 | 41 | HttpRequestMessage httpRequestMessage = new HttpRequestMessage(requestLine, messageHeaders, httpBody, httpQueryParameters); 42 | return httpRequestMessage; 43 | } 44 | 45 | /** 46 | * 读取请求发送的数据,并保存为byte数组设置到解析上下文中 47 | * 48 | * @param inputStream 49 | * @throws IOException 50 | */ 51 | private void getAndSetBytesBeforeBodyToContext(InputStream inputStream) throws IOException { 52 | byte[] bytes = copyRequestBytesBeforeBody(inputStream); 53 | HttpParserContext.setHttpMessageBytes(bytes); 54 | HttpParserContext.setBytesLengthBeforeBody(bytes.length); 55 | HttpParserContext.setInputStream(inputStream); 56 | } 57 | 58 | /** 59 | * 解析并构建RequestLine 60 | * 61 | * @return 62 | */ 63 | protected abstract RequestLine parseRequestLine(); 64 | 65 | /** 66 | * 解析并构建HTTP请求Headers集合 67 | * 68 | * @return 69 | */ 70 | protected abstract IMessageHeaders parseRequestHeaders(); 71 | 72 | /** 73 | * 解析并构建HTTP 请求Body 74 | * 75 | * @return 76 | */ 77 | protected abstract Optional parseRequestBody(); 78 | 79 | /** 80 | * 解析并构建QueryParameter集合 81 | * 82 | * @return 83 | */ 84 | protected abstract HttpQueryParameters parseHttpQueryParameters(); 85 | 86 | /** 87 | * 构造body(如果有)之前的字节数组 88 | * 89 | * @param inputStream 90 | * @return 91 | * @throws IOException 92 | */ 93 | private byte[] copyRequestBytesBeforeBody(InputStream inputStream) throws IOException { 94 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(inputStream.available()); 95 | int i = -1; 96 | byte[] temp = new byte[3]; 97 | while ((i = inputStream.read()) != -1) { 98 | byteArrayOutputStream.write(i); 99 | if ((char) i == '\r') { 100 | int len = inputStream.read(temp, 0, temp.length); 101 | byteArrayOutputStream.write(temp, 0, len); 102 | if ("\n\r\n".equals(new String(temp))) { 103 | break; 104 | } 105 | } 106 | } 107 | return byteArrayOutputStream.toByteArray(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/BodyInfo.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018/1/21 6 | */ 7 | public class BodyInfo { 8 | private String contentType; 9 | //Content-Type中的Charset:如UTF-8,GB2312等 10 | private String charset; 11 | //Content-Encoding:压缩格式,如gzip、deflate等 12 | private String encoding; 13 | private boolean hasBody; 14 | private long contentLength; 15 | private String transferEncoding; 16 | 17 | public BodyInfo() { 18 | } 19 | 20 | public BodyInfo(String contentType, String encoding, boolean hasBody, int contentLength) { 21 | this.contentType = contentType; 22 | this.charset = encoding; 23 | this.hasBody = hasBody; 24 | this.contentLength = contentLength; 25 | } 26 | 27 | public String getContentType() { 28 | return contentType; 29 | } 30 | 31 | public void setContentType(String contentType) { 32 | this.contentType = contentType; 33 | } 34 | 35 | public String getCharset() { 36 | return charset; 37 | } 38 | 39 | public void setCharset(String charset) { 40 | this.charset = charset; 41 | } 42 | 43 | public boolean isHasBody() { 44 | return hasBody; 45 | } 46 | 47 | public void setHasBody(boolean hasBody) { 48 | this.hasBody = hasBody; 49 | } 50 | 51 | public long getContentLength() { 52 | return contentLength; 53 | } 54 | 55 | public void setContentLength(long contentLength) { 56 | this.contentLength = contentLength; 57 | } 58 | 59 | public String getEncoding() { 60 | return encoding; 61 | } 62 | 63 | public void setEncoding(String encoding) { 64 | this.encoding = encoding; 65 | } 66 | 67 | public String getTransferEncoding() { 68 | return transferEncoding; 69 | } 70 | 71 | public void setTransferEncoding(String transferEncoding) { 72 | this.transferEncoding = transferEncoding; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/DefaultHttpBodyParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.body.HttpBody; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.io.InputStream; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/21 11 | */ 12 | public class DefaultHttpBodyParser implements HttpBodyParser { 13 | @Override 14 | public HttpBody parse() { 15 | long contentLength = HttpParserContext.getBodyInfo().getContentLength(); 16 | InputStream inputStream = HttpParserContext.getInputStream(); 17 | String contentType = HttpParserContext.getContentType(); 18 | String encoding = HttpParserContext.getEncoding(); 19 | String charset = getCharset(contentType); 20 | String transferEncoding = HttpParserContext.getTransferEncoding(); 21 | HttpBody httpBody = 22 | new HttpBody(inputStream, transferEncoding); 23 | httpBody.setCharSet(charset); 24 | httpBody.setEncoding(encoding); 25 | httpBody.setContentLength(contentLength); 26 | return httpBody; 27 | } 28 | 29 | /** 30 | * 获取encoding 31 | * 例如:Content-type: application/json; charset=utf-8 32 | * 33 | * @param contentType 34 | * @return 35 | */ 36 | private String getCharset(String contentType) { 37 | String charset = "utf-8"; 38 | if (StringUtils.isNotBlank(contentType) && contentType.contains(";")) { 39 | charset = contentType.split(";")[1].trim().replace("charset=", ""); 40 | } 41 | return charset; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/DefaultHttpHeaderParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.HttpConstants; 4 | import com.ljm.server.protocol.http.header.HttpHeader; 5 | import com.ljm.server.protocol.http.header.HttpMessageHeaders; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.UnsupportedEncodingException; 10 | 11 | import static com.ljm.server.protocol.http.HttpConstants.*; 12 | 13 | /** 14 | * @author 李佳明 https://github.com/pkpk1234 15 | * @date 2018-01-2018/1/16 16 | */ 17 | public class DefaultHttpHeaderParser implements HttpHeaderParser { 18 | private static final String SPLITTER = ":"; 19 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultHttpHeaderParser.class); 20 | 21 | @Override 22 | public HttpMessageHeaders parse() { 23 | try { 24 | String httpText = getHttpTextFromContext(); 25 | HttpMessageHeaders httpMessageHeaders = doParseHttpMessageHeaders(httpText); 26 | setHasBody(httpMessageHeaders); 27 | return httpMessageHeaders; 28 | } catch (UnsupportedEncodingException e) { 29 | throw new ParserException("Unsupported Encoding", e); 30 | } 31 | } 32 | 33 | /** 34 | * 从上下文获取bytes并转换为String 35 | * 36 | * @return 37 | * @throws UnsupportedEncodingException 38 | */ 39 | private String getHttpTextFromContext() throws UnsupportedEncodingException { 40 | byte[] bytes = HttpParserContext.getHttpMessageBytes(); 41 | return new String(bytes, "utf-8"); 42 | } 43 | 44 | /** 45 | * 解析Body之前的文本构建HttpHeader,并保存到HttpMessageHeaders中 46 | * 47 | * @param httpText 48 | * @return 49 | */ 50 | private HttpMessageHeaders doParseHttpMessageHeaders(String httpText) { 51 | HttpMessageHeaders httpMessageHeaders = new HttpMessageHeaders(); 52 | String[] lines = httpText.split(CRLF); 53 | //跳过第一行 54 | for (int i = 1; i < lines.length; i++) { 55 | String keyValue = lines[i]; 56 | if ("".equals(keyValue)) { 57 | break; 58 | } 59 | String[] temp = keyValue.split(SPLITTER); 60 | if (temp.length == 2) { 61 | httpMessageHeaders.addHeader(new HttpHeader(temp[0], temp[1].trim())); 62 | } 63 | } 64 | return httpMessageHeaders; 65 | } 66 | 67 | /** 68 | * 设置报文是否包含Body到上下文中 69 | */ 70 | private void setHasBody(HttpMessageHeaders httpMessageHeaders) { 71 | if (httpMessageHeaders.hasHeader(CONTENT_LENGTH) 72 | ) { 73 | HttpParserContext.setHasBody(true); 74 | if (httpMessageHeaders.hasHeader(CONTENT_LENGTH)) { 75 | HttpParserContext.getBodyInfo() 76 | .setContentLength(Integer.valueOf(httpMessageHeaders.getFirstHeader 77 | (CONTENT_LENGTH).getValue())); 78 | } 79 | if (httpMessageHeaders.hasHeader(CONTENT_ENCODING)) { 80 | HttpParserContext.setEncoding(httpMessageHeaders.getFirstHeader 81 | (CONTENT_ENCODING).getValue()); 82 | } 83 | 84 | } else if ((httpMessageHeaders.hasHeader(TRANSFER_ENCODING) 85 | && HttpConstants.ENCODING_CHUNKED.equals(httpMessageHeaders.getFirstHeader(TRANSFER_ENCODING).getValue()))) { 86 | HttpParserContext.setHasBody(true); 87 | if (httpMessageHeaders.hasHeader(TRANSFER_ENCODING)) { 88 | HttpParserContext.setTransferEncoding(httpMessageHeaders.getFirstHeader 89 | (TRANSFER_ENCODING).getValue()); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/DefaultHttpQueryParameterParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.HttpQueryParameter; 4 | import com.ljm.server.protocol.http.HttpQueryParameters; 5 | 6 | import static com.ljm.server.protocol.http.HttpConstants.KV_SPLITTER; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/15 11 | */ 12 | public class DefaultHttpQueryParameterParser implements HttpQueryParameterParser { 13 | private final HttpQueryParameters httpQueryParameters; 14 | private static final String SPLITTER = "&"; 15 | 16 | public DefaultHttpQueryParameterParser() { 17 | this.httpQueryParameters = new HttpQueryParameters(); 18 | } 19 | 20 | @Override 21 | public HttpQueryParameters parse() { 22 | String queryString = HttpParserContext.getRequestQueryString(); 23 | if (queryString != null) { 24 | String[] keyValues = queryString.split(SPLITTER); 25 | for (String keyValue : keyValues) { 26 | if (keyValue.contains(KV_SPLITTER)) { 27 | String[] temp = keyValue.split(KV_SPLITTER); 28 | if (temp.length == 2) { 29 | this.httpQueryParameters 30 | .addQueryParameter(new HttpQueryParameter(temp[0], temp[1])); 31 | } 32 | 33 | } 34 | } 35 | } 36 | return this.httpQueryParameters; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/DefaultHttpRequestLineParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.RequestLine; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | import java.net.URI; 7 | 8 | import static com.ljm.server.protocol.http.HttpConstants.*; 9 | /** 10 | * @author 李佳明 https://github.com/pkpk1234 11 | * @date 2018-01-2018/1/14 12 | *

13 | * Method SP Request-URI SP HTTP-Version CRLF 14 | */ 15 | public class DefaultHttpRequestLineParser implements HttpRequestLineParser { 16 | private static final String SPLITTER = "\\s+"; 17 | 18 | @Override 19 | public RequestLine parse() { 20 | String exceptionMsg = "startline format illegal:"; 21 | try { 22 | byte[] bytes = HttpParserContext.getHttpMessageBytes(); 23 | String httpString = new String(bytes, "utf-8"); 24 | //按CRLF将字符串拆分为长度为2的数组,取第一个值即为startLine 25 | String startLine = httpString.split(CRLF, 2)[0]; 26 | //去掉末尾的CRLF和空格,转化为Method SP Request-URI SP HTTP-Version 27 | String str = startLine.replaceAll(CRLF, "").trim(); 28 | String[] parts = str.split(SPLITTER); 29 | //数组格式{Method,Request-URI,HTTP-Version} 30 | if (parts.length == 3) { 31 | String method = parts[0]; 32 | URI uri = URI.create(parts[1]); 33 | HttpParserContext.setRequestQueryString(uri.getQuery()); 34 | String httpVersion = parts[2]; 35 | return new RequestLine(method, uri, httpVersion); 36 | } 37 | exceptionMsg += startLine; 38 | } catch (UnsupportedEncodingException e) { 39 | throw new ParserException("Unsupported Encoding", e); 40 | } 41 | //如果不满足RequestLine的格式,抛出异常 42 | throw new ParserException(exceptionMsg); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/DefaultHttpRequestMessageParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.HttpQueryParameters; 4 | import com.ljm.server.protocol.http.RequestLine; 5 | import com.ljm.server.protocol.http.body.HttpBody; 6 | import com.ljm.server.protocol.http.header.HttpMessageHeaders; 7 | import com.ljm.server.protocol.http.header.IMessageHeaders; 8 | 9 | import java.util.Optional; 10 | 11 | import static com.ljm.server.protocol.http.HttpConstants.CONTENT_TYPE; 12 | 13 | /** 14 | * @author 李佳明 https://github.com/pkpk1234 15 | * @date 2018-01-2018/1/14 16 | */ 17 | public class DefaultHttpRequestMessageParser extends AbstractHttpRequestMessageParser { 18 | private final HttpRequestLineParser httpRequestLineParser; 19 | private final HttpQueryParameterParser httpQueryParameterParser; 20 | private final HttpHeaderParser httpHeaderParser; 21 | private final HttpBodyParser httpBodyParser; 22 | 23 | public DefaultHttpRequestMessageParser(HttpRequestLineParser httpRequestLineParser, 24 | HttpQueryParameterParser httpQueryParameterParser, 25 | HttpHeaderParser httpHeaderParser, 26 | HttpBodyParser httpBodyParser) { 27 | this.httpRequestLineParser = httpRequestLineParser; 28 | this.httpQueryParameterParser = httpQueryParameterParser; 29 | this.httpHeaderParser = httpHeaderParser; 30 | this.httpBodyParser = httpBodyParser; 31 | } 32 | 33 | @Override 34 | protected RequestLine parseRequestLine() { 35 | RequestLine requestLine = this.httpRequestLineParser.parse(); 36 | HttpParserContext.setHttpMethod(requestLine.getMethod()); 37 | HttpParserContext.setRequestQueryString(requestLine.getRequestURI().getQuery()); 38 | return requestLine; 39 | } 40 | 41 | @Override 42 | protected HttpQueryParameters parseHttpQueryParameters() { 43 | return this.httpQueryParameterParser.parse(); 44 | } 45 | 46 | @Override 47 | protected IMessageHeaders parseRequestHeaders() { 48 | HttpMessageHeaders httpMessageHeaders = this.httpHeaderParser.parse(); 49 | if (httpMessageHeaders.hasHeader(CONTENT_TYPE)) { 50 | HttpParserContext.setContentType(httpMessageHeaders.getFirstHeader(CONTENT_TYPE).getValue()); 51 | } 52 | return httpMessageHeaders; 53 | } 54 | 55 | @Override 56 | protected Optional parseRequestBody() { 57 | if (isHasBodyMethod()) { 58 | HttpBody httpBody = this.httpBodyParser.parse(); 59 | return Optional.ofNullable(httpBody); 60 | } 61 | return Optional.empty(); 62 | } 63 | 64 | /** 65 | * 判断HTTP请求是否有Body,只支持POST和PUT 66 | * @return 67 | */ 68 | private boolean isHasBodyMethod() { 69 | return ("POST".equals(HttpParserContext.getHttpMethod()) 70 | || "PUT".equals(HttpParserContext.getHttpMethod())) 71 | && HttpParserContext.getHasBody(); 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/HttpBodyParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.body.HttpBody; 4 | 5 | /** 6 | * @author 李佳明 https://github.com/pkpk1234 7 | * @date 2018-01-2018/1/14 8 | */ 9 | public interface HttpBodyParser { 10 | /** 11 | * 解析并构建HttpBody对象 12 | * 13 | * @return 14 | */ 15 | HttpBody parse(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/HttpHeaderParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.header.HttpMessageHeaders; 4 | 5 | /** 6 | * @author 李佳明 https://github.com/pkpk1234 7 | * @date 2018-01-2018/1/14 8 | */ 9 | public interface HttpHeaderParser { 10 | /** 11 | * 解析并返回HttpMessageHeader集合 12 | * 13 | * @return 14 | */ 15 | HttpMessageHeaders parse(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/HttpParserContext.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.HttpConstants; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.io.InputStream; 7 | 8 | /** 9 | * HTTP Message解析上下文 10 | * 11 | * @author 李佳明 https://github.com/pkpk1234 12 | */ 13 | public class HttpParserContext { 14 | 15 | private static final ThreadLocal HTTP_MESSAGE_BYTES = new ThreadLocal<>(); 16 | private static final ThreadLocal REQUEST_QUERY_STRING = new ThreadLocal<>(); 17 | private static final ThreadLocal BODY_INFO = ThreadLocal.withInitial(() -> new BodyInfo()); 18 | private static final ThreadLocal BYTES_LENGTH_BEFORE_BODY = new ThreadLocal<>(); 19 | private static final ThreadLocal HTTP_METHOD = new ThreadLocal<>(); 20 | private static final ThreadLocal INPUT_STREAM = new ThreadLocal<>(); 21 | 22 | public static byte[] getHttpMessageBytes() { 23 | return HTTP_MESSAGE_BYTES.get(); 24 | } 25 | 26 | public static void setHttpMessageBytes(byte[] iHttpMessageBytes) { 27 | HTTP_MESSAGE_BYTES.set(iHttpMessageBytes); 28 | } 29 | 30 | public static String getRequestQueryString() { 31 | return REQUEST_QUERY_STRING.get(); 32 | } 33 | 34 | public static void setRequestQueryString(String iRequestQueryString) { 35 | REQUEST_QUERY_STRING.set(iRequestQueryString); 36 | } 37 | 38 | public static boolean getHasBody() { 39 | return BODY_INFO.get().isHasBody(); 40 | } 41 | 42 | public static void setHasBody(boolean iHasBody) { 43 | BODY_INFO.get().setHasBody(iHasBody); 44 | } 45 | 46 | public static int getBytesLengthBeforeBody() { 47 | return BYTES_LENGTH_BEFORE_BODY.get(); 48 | } 49 | 50 | public static void setBytesLengthBeforeBody(int iBytesLengthBeforeBody) { 51 | BYTES_LENGTH_BEFORE_BODY.set(iBytesLengthBeforeBody); 52 | } 53 | 54 | public static String getContentType() { 55 | return BODY_INFO.get().getContentType(); 56 | } 57 | 58 | public static void setContentType(String contentType) { 59 | BODY_INFO.get().setContentType(contentType); 60 | } 61 | 62 | public static String getHttpMethod() { 63 | return HTTP_METHOD.get(); 64 | } 65 | 66 | public static void setHttpMethod(String method) { 67 | HTTP_METHOD.set(method); 68 | } 69 | 70 | public static String getCharset() { 71 | return BODY_INFO.get().getCharset(); 72 | } 73 | 74 | public static void setCharset(String charset) { 75 | BODY_INFO.get().setCharset(charset); 76 | } 77 | 78 | public static String getEncoding() { 79 | return BODY_INFO.get().getEncoding(); 80 | } 81 | 82 | public static void setEncoding(String encoding) { 83 | BODY_INFO.get().setEncoding(encoding); 84 | } 85 | 86 | public static InputStream getInputStream() { 87 | return INPUT_STREAM.get(); 88 | } 89 | 90 | public static void setInputStream(InputStream inputStream) { 91 | INPUT_STREAM.set(inputStream); 92 | } 93 | 94 | public static BodyInfo getBodyInfo() { 95 | return BODY_INFO.get(); 96 | } 97 | 98 | public static void setBodyInfo(BodyInfo bodyInfo) { 99 | BODY_INFO.set(bodyInfo); 100 | } 101 | 102 | public static void setTransferEncoding(String transferEncoding) { 103 | BODY_INFO.get().setTransferEncoding(transferEncoding); 104 | } 105 | 106 | public static String getTransferEncoding() { 107 | String transferEncoding = BODY_INFO.get().getTransferEncoding(); 108 | return StringUtils.isBlank(transferEncoding) 109 | ? HttpConstants.ENCODING_IDENTITY : transferEncoding; 110 | } 111 | 112 | public static void removeAll() { 113 | HTTP_MESSAGE_BYTES.remove(); 114 | REQUEST_QUERY_STRING.remove(); 115 | BODY_INFO.remove(); 116 | BYTES_LENGTH_BEFORE_BODY.remove(); 117 | HTTP_METHOD.remove(); 118 | INPUT_STREAM.remove(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/HttpQueryParameterParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.HttpQueryParameters; 4 | 5 | /** 6 | * @author 李佳明 https://github.com/pkpk1234 7 | * @date 2018-01-2018/1/15 8 | */ 9 | public interface HttpQueryParameterParser { 10 | /** 11 | * 解析QueryString,返回HttpQueryParameter集合 12 | * 13 | * @return 14 | */ 15 | HttpQueryParameters parse(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/HttpRequestLineParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.RequestLine; 4 | 5 | /** 6 | * @author 李佳明 https://github.com/pkpk1234 7 | * @date 2018-01-2018/1/14 8 | */ 9 | public interface HttpRequestLineParser { 10 | /** 11 | * 解析start line,并返回RequestLine对象 12 | *

13 | * Method SP Request-URI SP HTTP-Version CRLF 14 | * 15 | * @return 16 | */ 17 | RequestLine parse(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/HttpRequestMessageParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.HttpMessage; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/14 11 | */ 12 | public interface HttpRequestMessageParser { 13 | /** 14 | * 解析输入流中的内容,并构建Http Message对象 15 | * 16 | * @param inputStream 17 | * @return 18 | * @throws IOException 19 | */ 20 | HttpMessage parse(InputStream inputStream) throws IOException; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/parser/ParserException.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/14 6 | */ 7 | public class ParserException extends RuntimeException { 8 | public ParserException() { 9 | super(); 10 | } 11 | 12 | public ParserException(String message) { 13 | super(message); 14 | } 15 | 16 | public ParserException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public ParserException(Throwable cause) { 21 | super(cause); 22 | } 23 | 24 | protected ParserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 25 | super(message, cause, enableSuppression, writableStackTrace); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/response/HttpResponseConstants.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.response; 2 | 3 | import com.ljm.server.protocol.http.HttpResponseMessage; 4 | import com.ljm.server.protocol.http.header.HttpMessageHeaders; 5 | 6 | /** 7 | * @author 李佳明 https://github.com/pkpk1234 8 | * @date 2018/1/22 9 | */ 10 | public interface HttpResponseConstants { 11 | HttpResponseMessage HTTP_404 = HttpResponseMessageBuilder.builder() 12 | .withResponseLine(ResponseLineConstants.RES_404) 13 | .withMessageHeaders(HttpMessageHeaders.newBuilder() 14 | .addHeader("Content-Type", "text/html").build()) 15 | .build(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/response/HttpResponseMessageBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.response; 2 | 3 | import com.ljm.server.protocol.http.HttpResponseMessage; 4 | import com.ljm.server.protocol.http.ResponseLine; 5 | import com.ljm.server.protocol.http.body.HttpBody; 6 | import com.ljm.server.protocol.http.header.IMessageHeaders; 7 | 8 | import java.util.Optional; 9 | 10 | /** 11 | * @author 李佳明 https://github.com/pkpk1234 12 | * @date 2018/1/22 13 | */ 14 | public final class HttpResponseMessageBuilder { 15 | private ResponseLine responseLine; 16 | private IMessageHeaders messageHeaders; 17 | private Optional httpBody = Optional.empty(); 18 | 19 | private HttpResponseMessageBuilder() { 20 | } 21 | 22 | public static HttpResponseMessageBuilder builder() { 23 | return new HttpResponseMessageBuilder(); 24 | } 25 | 26 | public HttpResponseMessageBuilder withResponseLine(ResponseLine responseLine) { 27 | this.responseLine = responseLine; 28 | return this; 29 | } 30 | 31 | public HttpResponseMessageBuilder withMessageHeaders(IMessageHeaders messageHeaders) { 32 | this.messageHeaders = messageHeaders; 33 | return this; 34 | } 35 | 36 | public HttpResponseMessageBuilder withHttpBody(Optional httpBody) { 37 | this.httpBody = httpBody; 38 | return this; 39 | } 40 | 41 | public HttpResponseMessage build() { 42 | return new HttpResponseMessage(responseLine, messageHeaders, httpBody); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/response/HttpResponseMessageWriter.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.response; 2 | 3 | import com.ljm.server.io.connection.Connection; 4 | import com.ljm.server.protocol.http.HttpResponseMessage; 5 | import com.ljm.server.protocol.http.ResponseLine; 6 | import com.ljm.server.protocol.http.body.HttpBody; 7 | import com.ljm.server.protocol.http.header.IMessageHeaders; 8 | import org.apache.commons.io.IOUtils; 9 | 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | import java.util.Optional; 13 | 14 | /** 15 | * @author 李佳明 https://github.com/pkpk1234 16 | * @date 2018-01-22 17 | */ 18 | public class HttpResponseMessageWriter { 19 | public void write(HttpResponseMessage httpResponseMessage, Connection connection) 20 | throws IOException { 21 | OutputStream outputStream = connection.getOutputStream(); 22 | ResponseLine responseLine = httpResponseMessage.getResponseLine(); 23 | String responseLineString = responseLine.asString(); 24 | write(outputStream, responseLineString); 25 | 26 | IMessageHeaders headers = httpResponseMessage.getMessageHeaders(); 27 | String headersString = headers.asString(); 28 | write(outputStream, headersString); 29 | 30 | Optional opHttpBody = httpResponseMessage.getHttpBody(); 31 | if (opHttpBody.isPresent()) { 32 | 33 | IOUtils.copy(opHttpBody.get().getInputStream(), outputStream); 34 | } 35 | outputStream.flush(); 36 | } 37 | 38 | private void write(OutputStream outputStream, String message) throws IOException { 39 | outputStream.write(message.getBytes("utf-8")); 40 | outputStream.write("\r\n".getBytes()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/response/HttpStatus.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.response; 2 | 3 | /** 4 | * @author 李佳明 https://github.com/pkpk1234 5 | * @date 2018-01-2018/1/22 6 | */ 7 | @SuppressWarnings("AlibabaEnumConstantsMustHaveComment") 8 | public enum HttpStatus { 9 | STATUS_404(404, "Not Found"), STATUS_200(200, "OK"); 10 | private int statusCode; 11 | private String reason; 12 | 13 | HttpStatus(int statusCode, String reason) { 14 | this.statusCode = statusCode; 15 | this.reason = reason; 16 | } 17 | 18 | public int getStatusCode() { 19 | return statusCode; 20 | } 21 | 22 | public String getReason() { 23 | return reason; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ljm/server/protocol/http/response/ResponseLineConstants.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.response; 2 | 3 | import com.ljm.server.protocol.http.HttpProtocol; 4 | import com.ljm.server.protocol.http.ResponseLine; 5 | 6 | /** 7 | * @author 李佳明 https://github.com/pkpk1234 8 | * @date 2018/1/22 9 | */ 10 | public interface ResponseLineConstants { 11 | ResponseLine RES_404 = new ResponseLine(HttpProtocol.VERSION11.toString(), 12 | HttpStatus.STATUS_404.getStatusCode(), HttpStatus.STATUS_404.getReason()); 13 | 14 | ResponseLine RES_200 = new ResponseLine(HttpProtocol.VERSION11.toString(), 15 | HttpStatus.STATUS_200.getStatusCode(), HttpStatus.STATUS_200.getReason()); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | front.log 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/mime-mapping.properties: -------------------------------------------------------------------------------- 1 | #拷贝自tomcat 2 | .123=application/vnd.lotus-1-2-3 3 | .3dml=text/vnd.in3d.3dml 4 | .3ds=image/x-3ds 5 | .3g2=video/3gpp2 6 | .3gp=video/3gpp 7 | .7z=application/x-7z-compressed 8 | .aab=application/x-authorware-bin 9 | .aac=audio/x-aac 10 | .aam=application/x-authorware-map 11 | .aas=application/x-authorware-seg 12 | .abs=audio/x-mpeg 13 | .abw=application/x-abiword 14 | .ac=application/pkix-attr-cert 15 | .acc=application/vnd.americandynamics.acc 16 | .ace=application/x-ace-compressed 17 | .acu=application/vnd.acucobol 18 | .acutc=application/vnd.acucorp 19 | .adp=audio/adpcm 20 | .aep=application/vnd.audiograph 21 | .afm=application/x-font-type1 22 | .afp=application/vnd.ibm.modcap 23 | .ahead=application/vnd.ahead.space 24 | .ai=application/postscript 25 | .aif=audio/x-aiff 26 | .aifc=audio/x-aiff 27 | .aiff=audio/x-aiff 28 | .aim=application/x-aim 29 | .air=application/vnd.adobe.air-application-installer-package+zip 30 | .ait=application/vnd.dvb.ait 31 | .ami=application/vnd.amiga.ami 32 | .anx=application/annodex 33 | .apk=application/vnd.android.package-archive 34 | .appcache=text/cache-manifest 35 | .application=application/x-ms-application 36 | .apr=application/vnd.lotus-approach 37 | .arc=application/x-freearc 38 | .art=image/x-jg 39 | .asc=application/pgp-signature 40 | .asf=video/x-ms-asf 41 | .asm=text/x-asm 42 | .aso=application/vnd.accpac.simply.aso 43 | .asx=video/x-ms-asf 44 | .atc=application/vnd.acucorp 45 | .atom=application/atom+xml 46 | .atomcat=application/atomcat+xml 47 | .atomsvc=application/atomsvc+xml 48 | .atx=application/vnd.antix.game-component 49 | .au=audio/basic 50 | .avi=video/x-msvideo 51 | .avx=video/x-rad-screenplay 52 | .aw=application/applixware 53 | .axa=audio/annodex 54 | .axv=video/annodex 55 | .azf=application/vnd.airzip.filesecure.azf 56 | .azs=application/vnd.airzip.filesecure.azs 57 | .azw=application/vnd.amazon.ebook 58 | .bat=application/x-msdownload 59 | .bcpio=application/x-bcpio 60 | .bdf=application/x-font-bdf 61 | .bdm=application/vnd.syncml.dm+wbxml 62 | .bed=application/vnd.realvnc.bed 63 | .bh2=application/vnd.fujitsu.oasysprs 64 | .bin=application/octet-stream 65 | .blb=application/x-blorb 66 | .blorb=application/x-blorb 67 | .bmi=application/vnd.bmi 68 | .bmp=image/bmp 69 | .body=text/html 70 | .book=application/vnd.framemaker 71 | .box=application/vnd.previewsystems.box 72 | .boz=application/x-bzip2 73 | .bpk=application/octet-stream 74 | .btif=image/prs.btif 75 | .bz=application/x-bzip 76 | .bz2=application/x-bzip2 77 | .c=text/x-c 78 | .c11amc=application/vnd.cluetrust.cartomobile-config 79 | .c11amz=application/vnd.cluetrust.cartomobile-config-pkg 80 | .c4d=application/vnd.clonk.c4group 81 | .c4f=application/vnd.clonk.c4group 82 | .c4g=application/vnd.clonk.c4group 83 | .c4p=application/vnd.clonk.c4group 84 | .c4u=application/vnd.clonk.c4group 85 | .cab=application/vnd.ms-cab-compressed 86 | .caf=audio/x-caf 87 | .cap=application/vnd.tcpdump.pcap 88 | .car=application/vnd.curl.car 89 | .cat=application/vnd.ms-pki.seccat 90 | .cb7=application/x-cbr 91 | .cba=application/x-cbr 92 | .cbr=application/x-cbr 93 | .cbt=application/x-cbr 94 | .cbz=application/x-cbr 95 | .cc=text/x-c 96 | .cct=application/x-director 97 | .ccxml=application/ccxml+xml 98 | .cdbcmsg=application/vnd.contact.cmsg 99 | .cdf=application/x-cdf 100 | .cdkey=application/vnd.mediastation.cdkey 101 | .cdmia=application/cdmi-capability 102 | .cdmic=application/cdmi-container 103 | .cdmid=application/cdmi-domain 104 | .cdmio=application/cdmi-object 105 | .cdmiq=application/cdmi-queue 106 | .cdx=chemical/x-cdx 107 | .cdxml=application/vnd.chemdraw+xml 108 | .cdy=application/vnd.cinderella 109 | .cer=application/pkix-cert 110 | .cfs=application/x-cfs-compressed 111 | .cgm=image/cgm 112 | .chat=application/x-chat 113 | .chm=application/vnd.ms-htmlhelp 114 | .chrt=application/vnd.kde.kchart 115 | .cif=chemical/x-cif 116 | .cii=application/vnd.anser-web-certificate-issue-initiation 117 | .cil=application/vnd.ms-artgalry 118 | .cla=application/vnd.claymore 119 | .class=application/java 120 | .clkk=application/vnd.crick.clicker.keyboard 121 | .clkp=application/vnd.crick.clicker.palette 122 | .clkt=application/vnd.crick.clicker.template 123 | .clkw=application/vnd.crick.clicker.wordbank 124 | .clkx=application/vnd.crick.clicker 125 | .clp=application/x-msclip 126 | .cmc=application/vnd.cosmocaller 127 | .cmdf=chemical/x-cmdf 128 | .cml=chemical/x-cml 129 | .cmp=application/vnd.yellowriver-custom-menu 130 | .cmx=image/x-cmx 131 | .cod=application/vnd.rim.cod 132 | .com=application/x-msdownload 133 | .conf=text/plain 134 | .cpio=application/x-cpio 135 | .cpp=text/x-c 136 | .cpt=application/mac-compactpro 137 | .crd=application/x-mscardfile 138 | .crl=application/pkix-crl 139 | .crt=application/x-x509-ca-cert 140 | .cryptonote=application/vnd.rig.cryptonote 141 | .csh=application/x-csh 142 | .csml=chemical/x-csml 143 | .csp=application/vnd.commonspace 144 | .css=text/css 145 | .cst=application/x-director 146 | .csv=text/csv 147 | .cu=application/cu-seeme 148 | .curl=text/vnd.curl 149 | .cww=application/prs.cww 150 | .cxt=application/x-director 151 | .cxx=text/x-c 152 | .dae=model/vnd.collada+xml 153 | .daf=application/vnd.mobius.daf 154 | .dart=application/vnd.dart 155 | .dataless=application/vnd.fdsn.seed 156 | .davmount=application/davmount+xml 157 | .dbk=application/docbook+xml 158 | .dcr=application/x-director 159 | .dcurl=text/vnd.curl.dcurl 160 | .dd2=application/vnd.oma.dd2+xml 161 | .ddd=application/vnd.fujixerox.ddd 162 | .deb=application/x-debian-package 163 | .def=text/plain 164 | .deploy=application/octet-stream 165 | .der=application/x-x509-ca-cert 166 | .dfac=application/vnd.dreamfactory 167 | .dgc=application/x-dgc-compressed 168 | .dib=image/bmp 169 | .dic=text/x-c 170 | .dir=application/x-director 171 | .dis=application/vnd.mobius.dis 172 | .dist=application/octet-stream 173 | .distz=application/octet-stream 174 | .djv=image/vnd.djvu 175 | .djvu=image/vnd.djvu 176 | .dll=application/x-msdownload 177 | .dmg=application/x-apple-diskimage 178 | .dmp=application/vnd.tcpdump.pcap 179 | .dms=application/octet-stream 180 | .dna=application/vnd.dna 181 | .doc=application/msword 182 | .docm=application/vnd.ms-word.document.macroenabled.12 183 | .docx=application/vnd.openxmlformats-officedocument.wordprocessingml.document 184 | .dot=application/msword 185 | .dotm=application/vnd.ms-word.template.macroenabled.12 186 | .dotx=application/vnd.openxmlformats-officedocument.wordprocessingml.template 187 | .dp=application/vnd.osgi.dp 188 | .dpg=application/vnd.dpgraph 189 | .dra=audio/vnd.dra 190 | .dsc=text/prs.lines.tag 191 | .dssc=application/dssc+der 192 | .dtb=application/x-dtbook+xml 193 | .dtd=application/xml-dtd 194 | .dts=audio/vnd.dts 195 | .dtshd=audio/vnd.dts.hd 196 | .dump=application/octet-stream 197 | .dv=video/x-dv 198 | .dvb=video/vnd.dvb.file 199 | .dvi=application/x-dvi 200 | .dwf=model/vnd.dwf 201 | .dwg=image/vnd.dwg 202 | .dxf=image/vnd.dxf 203 | .dxp=application/vnd.spotfire.dxp 204 | .dxr=application/x-director 205 | .ecelp4800=audio/vnd.nuera.ecelp4800 206 | .ecelp7470=audio/vnd.nuera.ecelp7470 207 | .ecelp9600=audio/vnd.nuera.ecelp9600 208 | .ecma=application/ecmascript 209 | .edm=application/vnd.novadigm.edm 210 | .edx=application/vnd.novadigm.edx 211 | .efif=application/vnd.picsel 212 | .ei6=application/vnd.pg.osasli 213 | .elc=application/octet-stream 214 | .emf=application/x-msmetafile 215 | .eml=message/rfc822 216 | .emma=application/emma+xml 217 | .emz=application/x-msmetafile 218 | .eol=audio/vnd.digital-winds 219 | .eot=application/vnd.ms-fontobject 220 | .eps=application/postscript 221 | .epub=application/epub+zip 222 | .es3=application/vnd.eszigno3+xml 223 | .esa=application/vnd.osgi.subsystem 224 | .esf=application/vnd.epson.esf 225 | .et3=application/vnd.eszigno3+xml 226 | .etx=text/x-setext 227 | .eva=application/x-eva 228 | .evy=application/x-envoy 229 | .exe=application/octet-stream 230 | .exi=application/exi 231 | .ext=application/vnd.novadigm.ext 232 | .ez=application/andrew-inset 233 | .ez2=application/vnd.ezpix-album 234 | .ez3=application/vnd.ezpix-package 235 | .f=text/x-fortran 236 | .f4v=video/x-f4v 237 | .f77=text/x-fortran 238 | .f90=text/x-fortran 239 | .fbs=image/vnd.fastbidsheet 240 | .fcdt=application/vnd.adobe.formscentral.fcdt 241 | .fcs=application/vnd.isac.fcs 242 | .fdf=application/vnd.fdf 243 | .fe_launch=application/vnd.denovo.fcselayout-link 244 | .fg5=application/vnd.fujitsu.oasysgp 245 | .fgd=application/x-director 246 | .fh=image/x-freehand 247 | .fh4=image/x-freehand 248 | .fh5=image/x-freehand 249 | .fh7=image/x-freehand 250 | .fhc=image/x-freehand 251 | .fig=application/x-xfig 252 | .flac=audio/flac 253 | .fli=video/x-fli 254 | .flo=application/vnd.micrografx.flo 255 | .flv=video/x-flv 256 | .flw=application/vnd.kde.kivio 257 | .flx=text/vnd.fmi.flexstor 258 | .fly=text/vnd.fly 259 | .fm=application/vnd.framemaker 260 | .fnc=application/vnd.frogans.fnc 261 | .for=text/x-fortran 262 | .fpx=image/vnd.fpx 263 | .frame=application/vnd.framemaker 264 | .fsc=application/vnd.fsc.weblaunch 265 | .fst=image/vnd.fst 266 | .ftc=application/vnd.fluxtime.clip 267 | .fti=application/vnd.anser-web-funds-transfer-initiation 268 | .fvt=video/vnd.fvt 269 | .fxp=application/vnd.adobe.fxp 270 | .fxpl=application/vnd.adobe.fxp 271 | .fzs=application/vnd.fuzzysheet 272 | .g2w=application/vnd.geoplan 273 | .g3=image/g3fax 274 | .g3w=application/vnd.geospace 275 | .gac=application/vnd.groove-account 276 | .gam=application/x-tads 277 | .gbr=application/rpki-ghostbusters 278 | .gca=application/x-gca-compressed 279 | .gdl=model/vnd.gdl 280 | .geo=application/vnd.dynageo 281 | .gex=application/vnd.geometry-explorer 282 | .ggb=application/vnd.geogebra.file 283 | .ggt=application/vnd.geogebra.tool 284 | .ghf=application/vnd.groove-help 285 | .gif=image/gif 286 | .gim=application/vnd.groove-identity-message 287 | .gml=application/gml+xml 288 | .gmx=application/vnd.gmx 289 | .gnumeric=application/x-gnumeric 290 | .gph=application/vnd.flographit 291 | .gpx=application/gpx+xml 292 | .gqf=application/vnd.grafeq 293 | .gqs=application/vnd.grafeq 294 | .gram=application/srgs 295 | .gramps=application/x-gramps-xml 296 | .gre=application/vnd.geometry-explorer 297 | .grv=application/vnd.groove-injector 298 | .grxml=application/srgs+xml 299 | .gsf=application/x-font-ghostscript 300 | .gtar=application/x-gtar 301 | .gtm=application/vnd.groove-tool-message 302 | .gtw=model/vnd.gtw 303 | .gv=text/vnd.graphviz 304 | .gxf=application/gxf 305 | .gxt=application/vnd.geonext 306 | .gz=application/x-gzip 307 | .h=text/x-c 308 | .h261=video/h261 309 | .h263=video/h263 310 | .h264=video/h264 311 | .hal=application/vnd.hal+xml 312 | .hbci=application/vnd.hbci 313 | .hdf=application/x-hdf 314 | .hh=text/x-c 315 | .hlp=application/winhlp 316 | .hpgl=application/vnd.hp-hpgl 317 | .hpid=application/vnd.hp-hpid 318 | .hps=application/vnd.hp-hps 319 | .hqx=application/mac-binhex40 320 | .htc=text/x-component 321 | .htke=application/vnd.kenameaapp 322 | .htm=text/html 323 | .html=text/html 324 | .hvd=application/vnd.yamaha.hv-dic 325 | .hvp=application/vnd.yamaha.hv-voice 326 | .hvs=application/vnd.yamaha.hv-script 327 | .i2g=application/vnd.intergeo 328 | .icc=application/vnd.iccprofile 329 | .ice=x-conference/x-cooltalk 330 | .icm=application/vnd.iccprofile 331 | .ico=image/x-icon 332 | .ics=text/calendar 333 | .ief=image/ief 334 | .ifb=text/calendar 335 | .ifm=application/vnd.shana.informed.formdata 336 | .iges=model/iges 337 | .igl=application/vnd.igloader 338 | .igm=application/vnd.insors.igm 339 | .igs=model/iges 340 | .igx=application/vnd.micrografx.igx 341 | .iif=application/vnd.shana.informed.interchange 342 | .imp=application/vnd.accpac.simply.imp 343 | .ims=application/vnd.ms-ims 344 | .in=text/plain 345 | .ink=application/inkml+xml 346 | .inkml=application/inkml+xml 347 | .install=application/x-install-instructions 348 | .iota=application/vnd.astraea-software.iota 349 | .ipfix=application/ipfix 350 | .ipk=application/vnd.shana.informed.package 351 | .irm=application/vnd.ibm.rights-management 352 | .irp=application/vnd.irepository.package+xml 353 | .iso=application/x-iso9660-image 354 | .itp=application/vnd.shana.informed.formtemplate 355 | .ivp=application/vnd.immervision-ivp 356 | .ivu=application/vnd.immervision-ivu 357 | .jad=text/vnd.sun.j2me.app-descriptor 358 | .jam=application/vnd.jam 359 | .jar=application/java-archive 360 | .java=text/x-java-source 361 | .jisp=application/vnd.jisp 362 | .jlt=application/vnd.hp-jlyt 363 | .jnlp=application/x-java-jnlp-file 364 | .joda=application/vnd.joost.joda-archive 365 | .jpe=image/jpeg 366 | .jpeg=image/jpeg 367 | .jpg=image/jpeg 368 | .jpgm=video/jpm 369 | .jpgv=video/jpeg 370 | .jpm=video/jpm 371 | .js=application/javascript 372 | .jsf=text/plain 373 | .json=application/json 374 | .jsonml=application/jsonml+json 375 | .jspf=text/plain 376 | .kar=audio/midi 377 | .karbon=application/vnd.kde.karbon 378 | .kfo=application/vnd.kde.kformula 379 | .kia=application/vnd.kidspiration 380 | .kml=application/vnd.google-earth.kml+xml 381 | .kmz=application/vnd.google-earth.kmz 382 | .kne=application/vnd.kinar 383 | .knp=application/vnd.kinar 384 | .kon=application/vnd.kde.kontour 385 | .kpr=application/vnd.kde.kpresenter 386 | .kpt=application/vnd.kde.kpresenter 387 | .kpxx=application/vnd.ds-keypoint 388 | .ksp=application/vnd.kde.kspread 389 | .ktr=application/vnd.kahootz 390 | .ktx=image/ktx 391 | .ktz=application/vnd.kahootz 392 | .kwd=application/vnd.kde.kword 393 | .kwt=application/vnd.kde.kword 394 | .lasxml=application/vnd.las.las+xml 395 | .latex=application/x-latex 396 | .lbd=application/vnd.llamagraphics.life-balance.desktop 397 | .lbe=application/vnd.llamagraphics.life-balance.exchange+xml 398 | .les=application/vnd.hhe.lesson-player 399 | .lha=application/x-lzh-compressed 400 | .link66=application/vnd.route66.link66+xml 401 | .list=text/plain 402 | .list3820=application/vnd.ibm.modcap 403 | .listafp=application/vnd.ibm.modcap 404 | .lnk=application/x-ms-shortcut 405 | .log=text/plain 406 | .lostxml=application/lost+xml 407 | .lrf=application/octet-stream 408 | .lrm=application/vnd.ms-lrm 409 | .ltf=application/vnd.frogans.ltf 410 | .lvp=audio/vnd.lucent.voice 411 | .lwp=application/vnd.lotus-wordpro 412 | .lzh=application/x-lzh-compressed 413 | .m13=application/x-msmediaview 414 | .m14=application/x-msmediaview 415 | .m1v=video/mpeg 416 | .m21=application/mp21 417 | .m2a=audio/mpeg 418 | .m2v=video/mpeg 419 | .m3a=audio/mpeg 420 | .m3u=audio/x-mpegurl 421 | .m3u8=application/vnd.apple.mpegurl 422 | .m4a=audio/mp4 423 | .m4b=audio/mp4 424 | .m4r=audio/mp4 425 | .m4u=video/vnd.mpegurl 426 | .m4v=video/mp4 427 | .ma=application/mathematica 428 | .mac=image/x-macpaint 429 | .mads=application/mads+xml 430 | .mag=application/vnd.ecowin.chart 431 | .maker=application/vnd.framemaker 432 | .man=text/troff 433 | .mar=application/octet-stream 434 | .mathml=application/mathml+xml 435 | .mb=application/mathematica 436 | .mbk=application/vnd.mobius.mbk 437 | .mbox=application/mbox 438 | .mc1=application/vnd.medcalcdata 439 | .mcd=application/vnd.mcd 440 | .mcurl=text/vnd.curl.mcurl 441 | .mdb=application/x-msaccess 442 | .mdi=image/vnd.ms-modi 443 | .me=text/troff 444 | .mesh=model/mesh 445 | .meta4=application/metalink4+xml 446 | .metalink=application/metalink+xml 447 | .mets=application/mets+xml 448 | .mfm=application/vnd.mfmp 449 | .mft=application/rpki-manifest 450 | .mgp=application/vnd.osgeo.mapguide.package 451 | .mgz=application/vnd.proteus.magazine 452 | .mid=audio/midi 453 | .midi=audio/midi 454 | .mie=application/x-mie 455 | .mif=application/x-mif 456 | .mime=message/rfc822 457 | .mj2=video/mj2 458 | .mjp2=video/mj2 459 | .mk3d=video/x-matroska 460 | .mka=audio/x-matroska 461 | .mks=video/x-matroska 462 | .mkv=video/x-matroska 463 | .mlp=application/vnd.dolby.mlp 464 | .mmd=application/vnd.chipnuts.karaoke-mmd 465 | .mmf=application/vnd.smaf 466 | .mmr=image/vnd.fujixerox.edmics-mmr 467 | .mng=video/x-mng 468 | .mny=application/x-msmoney 469 | .mobi=application/x-mobipocket-ebook 470 | .mods=application/mods+xml 471 | .mov=video/quicktime 472 | .movie=video/x-sgi-movie 473 | .mp1=audio/mpeg 474 | .mp2=audio/mpeg 475 | .mp21=application/mp21 476 | .mp2a=audio/mpeg 477 | .mp3=audio/mpeg 478 | .mp4=video/mp4 479 | .mp4a=audio/mp4 480 | .mp4s=application/mp4 481 | .mp4v=video/mp4 482 | .mpa=audio/mpeg 483 | .mpc=application/vnd.mophun.certificate 484 | .mpe=video/mpeg 485 | .mpeg=video/mpeg 486 | .mpega=audio/x-mpeg 487 | .mpg=video/mpeg 488 | .mpg4=video/mp4 489 | .mpga=audio/mpeg 490 | .mpkg=application/vnd.apple.installer+xml 491 | .mpm=application/vnd.blueice.multipass 492 | .mpn=application/vnd.mophun.application 493 | .mpp=application/vnd.ms-project 494 | .mpt=application/vnd.ms-project 495 | .mpv2=video/mpeg2 496 | .mpy=application/vnd.ibm.minipay 497 | .mqy=application/vnd.mobius.mqy 498 | .mrc=application/marc 499 | .mrcx=application/marcxml+xml 500 | .ms=text/troff 501 | .mscml=application/mediaservercontrol+xml 502 | .mseed=application/vnd.fdsn.mseed 503 | .mseq=application/vnd.mseq 504 | .msf=application/vnd.epson.msf 505 | .msh=model/mesh 506 | .msi=application/x-msdownload 507 | .msl=application/vnd.mobius.msl 508 | .msty=application/vnd.muvee.style 509 | .mts=model/vnd.mts 510 | .mus=application/vnd.musician 511 | .musicxml=application/vnd.recordare.musicxml+xml 512 | .mvb=application/x-msmediaview 513 | .mwf=application/vnd.mfer 514 | .mxf=application/mxf 515 | .mxl=application/vnd.recordare.musicxml 516 | .mxml=application/xv+xml 517 | .mxs=application/vnd.triscape.mxs 518 | .mxu=video/vnd.mpegurl 519 | .n-gage=application/vnd.nokia.n-gage.symbian.install 520 | .n3=text/n3 521 | .nb=application/mathematica 522 | .nbp=application/vnd.wolfram.player 523 | .nc=application/x-netcdf 524 | .ncx=application/x-dtbncx+xml 525 | .nfo=text/x-nfo 526 | .ngdat=application/vnd.nokia.n-gage.data 527 | .nitf=application/vnd.nitf 528 | .nlu=application/vnd.neurolanguage.nlu 529 | .nml=application/vnd.enliven 530 | .nnd=application/vnd.noblenet-directory 531 | .nns=application/vnd.noblenet-sealer 532 | .nnw=application/vnd.noblenet-web 533 | .npx=image/vnd.net-fpx 534 | .nsc=application/x-conference 535 | .nsf=application/vnd.lotus-notes 536 | .ntf=application/vnd.nitf 537 | .nzb=application/x-nzb 538 | .oa2=application/vnd.fujitsu.oasys2 539 | .oa3=application/vnd.fujitsu.oasys3 540 | .oas=application/vnd.fujitsu.oasys 541 | .obd=application/x-msbinder 542 | .obj=application/x-tgif 543 | .oda=application/oda 544 | . 545 | .odb=application/vnd.oasis.opendocument.database 546 | . 547 | .odc=application/vnd.oasis.opendocument.chart 548 | . 549 | .odf=application/vnd.oasis.opendocument.formula 550 | .odft=application/vnd.oasis.opendocument.formula-template 551 | . 552 | .odg=application/vnd.oasis.opendocument.graphics 553 | . 554 | .odi=application/vnd.oasis.opendocument.image 555 | . 556 | .odm=application/vnd.oasis.opendocument.text-master 557 | . 558 | .odp=application/vnd.oasis.opendocument.presentation 559 | . 560 | .ods=application/vnd.oasis.opendocument.spreadsheet 561 | . 562 | .odt=application/vnd.oasis.opendocument.text 563 | .oga=audio/ogg 564 | .ogg=audio/ogg 565 | .ogv=video/ogg 566 | . 567 | .ogx=application/ogg 568 | .omdoc=application/omdoc+xml 569 | .onepkg=application/onenote 570 | .onetmp=application/onenote 571 | .onetoc=application/onenote 572 | .onetoc2=application/onenote 573 | .opf=application/oebps-package+xml 574 | .opml=text/x-opml 575 | .oprc=application/vnd.palm 576 | .org=application/vnd.lotus-organizer 577 | .osf=application/vnd.yamaha.openscoreformat 578 | .osfpvg=application/vnd.yamaha.openscoreformat.osfpvg+xml 579 | .otc=application/vnd.oasis.opendocument.chart-template 580 | .otf=application/x-font-otf 581 | . 582 | .otg=application/vnd.oasis.opendocument.graphics-template 583 | . 584 | .oth=application/vnd.oasis.opendocument.text-web 585 | .oti=application/vnd.oasis.opendocument.image-template 586 | . 587 | .otp=application/vnd.oasis.opendocument.presentation-template 588 | . 589 | .ots=application/vnd.oasis.opendocument.spreadsheet-template 590 | . 591 | .ott=application/vnd.oasis.opendocument.text-template 592 | .oxps=application/oxps 593 | .oxt=application/vnd.openofficeorg.extension 594 | .p=text/x-pascal 595 | .p10=application/pkcs10 596 | .p12=application/x-pkcs12 597 | .p7b=application/x-pkcs7-certificates 598 | .p7c=application/pkcs7-mime 599 | .p7m=application/pkcs7-mime 600 | .p7r=application/x-pkcs7-certreqresp 601 | .p7s=application/pkcs7-signature 602 | .p8=application/pkcs8 603 | .pas=text/x-pascal 604 | .paw=application/vnd.pawaafile 605 | .pbd=application/vnd.powerbuilder6 606 | .pbm=image/x-portable-bitmap 607 | .pcap=application/vnd.tcpdump.pcap 608 | .pcf=application/x-font-pcf 609 | .pcl=application/vnd.hp-pcl 610 | .pclxl=application/vnd.hp-pclxl 611 | .pct=image/pict 612 | .pcurl=application/vnd.curl.pcurl 613 | .pcx=image/x-pcx 614 | .pdb=application/vnd.palm 615 | .pdf=application/pdf 616 | .pfa=application/x-font-type1 617 | .pfb=application/x-font-type1 618 | .pfm=application/x-font-type1 619 | .pfr=application/font-tdpfr 620 | .pfx=application/x-pkcs12 621 | .pgm=image/x-portable-graymap 622 | .pgn=application/x-chess-pgn 623 | .pgp=application/pgp-encrypted 624 | .pic=image/pict 625 | .pict=image/pict 626 | .pkg=application/octet-stream 627 | .pki=application/pkixcmp 628 | .pkipath=application/pkix-pkipath 629 | .plb=application/vnd.3gpp.pic-bw-large 630 | .plc=application/vnd.mobius.plc 631 | .plf=application/vnd.pocketlearn 632 | .pls=audio/x-scpls 633 | .pml=application/vnd.ctc-posml 634 | .png=image/png 635 | .pnm=image/x-portable-anymap 636 | .pnt=image/x-macpaint 637 | .portpkg=application/vnd.macports.portpkg 638 | .pot=application/vnd.ms-powerpoint 639 | .potm=application/vnd.ms-powerpoint.template.macroenabled.12 640 | .potx=application/vnd.openxmlformats-officedocument.presentationml.template 641 | .ppam=application/vnd.ms-powerpoint.addin.macroenabled.12 642 | .ppd=application/vnd.cups-ppd 643 | .ppm=image/x-portable-pixmap 644 | .pps=application/vnd.ms-powerpoint 645 | .ppsm=application/vnd.ms-powerpoint.slideshow.macroenabled.12 646 | .ppsx=application/vnd.openxmlformats-officedocument.presentationml.slideshow 647 | .ppt=application/vnd.ms-powerpoint 648 | .pptm=application/vnd.ms-powerpoint.presentation.macroenabled.12 649 | .pptx=application/vnd.openxmlformats-officedocument.presentationml.presentation 650 | .pqa=application/vnd.palm 651 | .prc=application/x-mobipocket-ebook 652 | .pre=application/vnd.lotus-freelance 653 | .prf=application/pics-rules 654 | .ps=application/postscript 655 | .psb=application/vnd.3gpp.pic-bw-small 656 | .psd=image/vnd.adobe.photoshop 657 | .psf=application/x-font-linux-psf 658 | .pskcxml=application/pskc+xml 659 | .ptid=application/vnd.pvi.ptid1 660 | .pub=application/x-mspublisher 661 | .pvb=application/vnd.3gpp.pic-bw-var 662 | .pwn=application/vnd.3m.post-it-notes 663 | .pya=audio/vnd.ms-playready.media.pya 664 | .pyv=video/vnd.ms-playready.media.pyv 665 | .qam=application/vnd.epson.quickanime 666 | .qbo=application/vnd.intu.qbo 667 | .qfx=application/vnd.intu.qfx 668 | .qps=application/vnd.publishare-delta-tree 669 | .qt=video/quicktime 670 | .qti=image/x-quicktime 671 | .qtif=image/x-quicktime 672 | .qwd=application/vnd.quark.quarkxpress 673 | .qwt=application/vnd.quark.quarkxpress 674 | .qxb=application/vnd.quark.quarkxpress 675 | .qxd=application/vnd.quark.quarkxpress 676 | .qxl=application/vnd.quark.quarkxpress 677 | .qxt=application/vnd.quark.quarkxpress 678 | .ra=audio/x-pn-realaudio 679 | .ram=audio/x-pn-realaudio 680 | .rar=application/x-rar-compressed 681 | .ras=image/x-cmu-raster 682 | .rcprofile=application/vnd.ipunplugged.rcprofile 683 | .rdf=application/rdf+xml 684 | .rdz=application/vnd.data-vision.rdz 685 | .rep=application/vnd.businessobjects 686 | .res=application/x-dtbresource+xml 687 | .rgb=image/x-rgb 688 | .rif=application/reginfo+xml 689 | .rip=audio/vnd.rip 690 | .ris=application/x-research-info-systems 691 | .rl=application/resource-lists+xml 692 | .rlc=image/vnd.fujixerox.edmics-rlc 693 | .rld=application/resource-lists-diff+xml 694 | .rm=application/vnd.rn-realmedia 695 | .rmi=audio/midi 696 | .rmp=audio/x-pn-realaudio-plugin 697 | .rms=application/vnd.jcp.javame.midlet-rms 698 | .rmvb=application/vnd.rn-realmedia-vbr 699 | .rnc=application/relax-ng-compact-syntax 700 | .roa=application/rpki-roa 701 | .roff=text/troff 702 | .rp9=application/vnd.cloanto.rp9 703 | .rpss=application/vnd.nokia.radio-presets 704 | .rpst=application/vnd.nokia.radio-preset 705 | .rq=application/sparql-query 706 | .rs=application/rls-services+xml 707 | .rsd=application/rsd+xml 708 | .rss=application/rss+xml 709 | .rtf=application/rtf 710 | .rtx=text/richtext 711 | .s=text/x-asm 712 | .s3m=audio/s3m 713 | .saf=application/vnd.yamaha.smaf-audio 714 | .sbml=application/sbml+xml 715 | .sc=application/vnd.ibm.secure-container 716 | .scd=application/x-msschedule 717 | .scm=application/vnd.lotus-screencam 718 | .scq=application/scvp-cv-request 719 | .scs=application/scvp-cv-response 720 | .scurl=text/vnd.curl.scurl 721 | .sda=application/vnd.stardivision.draw 722 | .sdc=application/vnd.stardivision.calc 723 | .sdd=application/vnd.stardivision.impress 724 | .sdkd=application/vnd.solent.sdkm+xml 725 | .sdkm=application/vnd.solent.sdkm+xml 726 | .sdp=application/sdp 727 | .sdw=application/vnd.stardivision.writer 728 | .see=application/vnd.seemail 729 | .seed=application/vnd.fdsn.seed 730 | .sema=application/vnd.sema 731 | .semd=application/vnd.semd 732 | .semf=application/vnd.semf 733 | .ser=application/java-serialized-object 734 | .setpay=application/set-payment-initiation 735 | .setreg=application/set-registration-initiation 736 | .sfd-hdstx=application/vnd.hydrostatix.sof-data 737 | .sfs=application/vnd.spotfire.sfs 738 | .sfv=text/x-sfv 739 | .sgi=image/sgi 740 | .sgl=application/vnd.stardivision.writer-global 741 | .sgm=text/sgml 742 | .sgml=text/sgml 743 | .sh=application/x-sh 744 | .shar=application/x-shar 745 | .shf=application/shf+xml 746 | . 749 | .sid=image/x-mrsid-image 750 | .sig=application/pgp-signature 751 | .sil=audio/silk 752 | .silo=model/mesh 753 | .sis=application/vnd.symbian.install 754 | .sisx=application/vnd.symbian.install 755 | .sit=application/x-stuffit 756 | .sitx=application/x-stuffitx 757 | .skd=application/vnd.koan 758 | .skm=application/vnd.koan 759 | .skp=application/vnd.koan 760 | .skt=application/vnd.koan 761 | .sldm=application/vnd.ms-powerpoint.slide.macroenabled.12 762 | .sldx=application/vnd.openxmlformats-officedocument.presentationml.slide 763 | .slt=application/vnd.epson.salt 764 | .sm=application/vnd.stepmania.stepchart 765 | .smf=application/vnd.stardivision.math 766 | .smi=application/smil+xml 767 | .smil=application/smil+xml 768 | .smv=video/x-smv 769 | .smzip=application/vnd.stepmania.package 770 | .snd=audio/basic 771 | .snf=application/x-font-snf 772 | .so=application/octet-stream 773 | .spc=application/x-pkcs7-certificates 774 | .spf=application/vnd.yamaha.smaf-phrase 775 | .spl=application/x-futuresplash 776 | .spot=text/vnd.in3d.spot 777 | .spp=application/scvp-vp-response 778 | .spq=application/scvp-vp-request 779 | .spx=audio/ogg 780 | .sql=application/x-sql 781 | .src=application/x-wais-source 782 | .srt=application/x-subrip 783 | .sru=application/sru+xml 784 | .srx=application/sparql-results+xml 785 | .ssdl=application/ssdl+xml 786 | .sse=application/vnd.kodak-descriptor 787 | .ssf=application/vnd.epson.ssf 788 | .ssml=application/ssml+xml 789 | .st=application/vnd.sailingtracker.track 790 | .stc=application/vnd.sun.xml.calc.template 791 | .std=application/vnd.sun.xml.draw.template 792 | .stf=application/vnd.wt.stf 793 | .sti=application/vnd.sun.xml.impress.template 794 | .stk=application/hyperstudio 795 | .stl=application/vnd.ms-pki.stl 796 | .str=application/vnd.pg.format 797 | .stw=application/vnd.sun.xml.writer.template 798 | .sub=text/vnd.dvb.subtitle 799 | .sus=application/vnd.sus-calendar 800 | .susp=application/vnd.sus-calendar 801 | .sv4cpio=application/x-sv4cpio 802 | .sv4crc=application/x-sv4crc 803 | .svc=application/vnd.dvb.service 804 | .svd=application/vnd.svd 805 | .svg=image/svg+xml 806 | .svgz=image/svg+xml 807 | .swa=application/x-director 808 | .swf=application/x-shockwave-flash 809 | .swi=application/vnd.aristanetworks.swi 810 | .sxc=application/vnd.sun.xml.calc 811 | .sxd=application/vnd.sun.xml.draw 812 | .sxg=application/vnd.sun.xml.writer.global 813 | .sxi=application/vnd.sun.xml.impress 814 | .sxm=application/vnd.sun.xml.math 815 | .sxw=application/vnd.sun.xml.writer 816 | .t=text/troff 817 | .t3=application/x-t3vm-image 818 | .taglet=application/vnd.mynfc 819 | .tao=application/vnd.tao.intent-module-archive 820 | .tar=application/x-tar 821 | .tcap=application/vnd.3gpp2.tcap 822 | .tcl=application/x-tcl 823 | .teacher=application/vnd.smart.teacher 824 | .tei=application/tei+xml 825 | .teicorpus=application/tei+xml 826 | .tex=application/x-tex 827 | .texi=application/x-texinfo 828 | .texinfo=application/x-texinfo 829 | .text=text/plain 830 | .tfi=application/thraud+xml 831 | .tfm=application/x-tex-tfm 832 | .tga=image/x-tga 833 | .thmx=application/vnd.ms-officetheme 834 | .tif=image/tiff 835 | .tiff=image/tiff 836 | .tmo=application/vnd.tmobile-livetv 837 | .torrent=application/x-bittorrent 838 | .tpl=application/vnd.groove-tool-template 839 | .tpt=application/vnd.trid.tpt 840 | .tr=text/troff 841 | .tra=application/vnd.trueapp 842 | .trm=application/x-msterminal 843 | .tsd=application/timestamped-data 844 | .tsv=text/tab-separated-values 845 | .ttc=application/x-font-ttf 846 | .ttf=application/x-font-ttf 847 | .ttl=text/turtle 848 | .twd=application/vnd.simtech-mindmapper 849 | .twds=application/vnd.simtech-mindmapper 850 | .txd=application/vnd.genomatix.tuxedo 851 | .txf=application/vnd.mobius.txf 852 | .txt=text/plain 853 | .u32=application/x-authorware-bin 854 | .udeb=application/x-debian-package 855 | .ufd=application/vnd.ufdl 856 | .ufdl=application/vnd.ufdl 857 | .ulw=audio/basic 858 | .ulx=application/x-glulx 859 | .umj=application/vnd.umajin 860 | .unityweb=application/vnd.unity 861 | .uoml=application/vnd.uoml+xml 862 | .uri=text/uri-list 863 | .uris=text/uri-list 864 | .urls=text/uri-list 865 | .ustar=application/x-ustar 866 | .utz=application/vnd.uiq.theme 867 | .uu=text/x-uuencode 868 | .uva=audio/vnd.dece.audio 869 | .uvd=application/vnd.dece.data 870 | .uvf=application/vnd.dece.data 871 | .uvg=image/vnd.dece.graphic 872 | .uvh=video/vnd.dece.hd 873 | .uvi=image/vnd.dece.graphic 874 | .uvm=video/vnd.dece.mobile 875 | .uvp=video/vnd.dece.pd 876 | .uvs=video/vnd.dece.sd 877 | .uvt=application/vnd.dece.ttml+xml 878 | .uvu=video/vnd.uvvu.mp4 879 | .uvv=video/vnd.dece.video 880 | .uvva=audio/vnd.dece.audio 881 | .uvvd=application/vnd.dece.data 882 | .uvvf=application/vnd.dece.data 883 | .uvvg=image/vnd.dece.graphic 884 | .uvvh=video/vnd.dece.hd 885 | .uvvi=image/vnd.dece.graphic 886 | .uvvm=video/vnd.dece.mobile 887 | .uvvp=video/vnd.dece.pd 888 | .uvvs=video/vnd.dece.sd 889 | .uvvt=application/vnd.dece.ttml+xml 890 | .uvvu=video/vnd.uvvu.mp4 891 | .uvvv=video/vnd.dece.video 892 | .uvvx=application/vnd.dece.unspecified 893 | .uvvz=application/vnd.dece.zip 894 | .uvx=application/vnd.dece.unspecified 895 | .uvz=application/vnd.dece.zip 896 | .vcard=text/vcard 897 | .vcd=application/x-cdlink 898 | .vcf=text/x-vcard 899 | .vcg=application/vnd.groove-vcard 900 | .vcs=text/x-vcalendar 901 | .vcx=application/vnd.vcx 902 | .vis=application/vnd.visionary 903 | .viv=video/vnd.vivo 904 | .vob=video/x-ms-vob 905 | .vor=application/vnd.stardivision.writer 906 | .vox=application/x-authorware-bin 907 | .vrml=model/vrml 908 | .vsd=application/vnd.visio 909 | .vsf=application/vnd.vsf 910 | .vss=application/vnd.visio 911 | .vst=application/vnd.visio 912 | .vsw=application/vnd.visio 913 | .vtu=model/vnd.vtu 914 | .vxml=application/voicexml+xml 915 | .w3d=application/x-director 916 | .wad=application/x-doom 917 | .wav=audio/x-wav 918 | .wax=audio/x-ms-wax 919 | . 920 | .wbmp=image/vnd.wap.wbmp 921 | .wbs=application/vnd.criticaltools.wbs+xml 922 | .wbxml=application/vnd.wap.wbxml 923 | .wcm=application/vnd.ms-works 924 | .wdb=application/vnd.ms-works 925 | .wdp=image/vnd.ms-photo 926 | .weba=audio/webm 927 | .webm=video/webm 928 | .webp=image/webp 929 | .wg=application/vnd.pmi.widget 930 | .wgt=application/widget 931 | .wks=application/vnd.ms-works 932 | .wm=video/x-ms-wm 933 | .wma=audio/x-ms-wma 934 | .wmd=application/x-ms-wmd 935 | .wmf=application/x-msmetafile 936 | . 937 | .wml=text/vnd.wap.wml 938 | . 939 | .wmlc=application/vnd.wap.wmlc 940 | . 941 | .wmls=text/vnd.wap.wmlscript 942 | . 943 | .wmlsc=application/vnd.wap.wmlscriptc 944 | .wmv=video/x-ms-wmv 945 | .wmx=video/x-ms-wmx 946 | .wmz=application/x-msmetafile 947 | .woff=application/x-font-woff 948 | .woff2=font/woff2 949 | .wpd=application/vnd.wordperfect 950 | .wpl=application/vnd.ms-wpl 951 | .wps=application/vnd.ms-works 952 | .wqd=application/vnd.wqd 953 | .wri=application/x-mswrite 954 | .wrl=model/vrml 955 | .wsdl=application/wsdl+xml 956 | .wspolicy=application/wspolicy+xml 957 | .wtb=application/vnd.webturbo 958 | .wvx=video/x-ms-wvx 959 | .x32=application/x-authorware-bin 960 | .x3d=model/x3d+xml 961 | .x3db=model/x3d+binary 962 | .x3dbz=model/x3d+binary 963 | .x3dv=model/x3d+vrml 964 | .x3dvz=model/x3d+vrml 965 | .x3dz=model/x3d+xml 966 | .xaml=application/xaml+xml 967 | .xap=application/x-silverlight-app 968 | .xar=application/vnd.xara 969 | .xbap=application/x-ms-xbap 970 | .xbd=application/vnd.fujixerox.docuworks.binder 971 | .xbm=image/x-xbitmap 972 | .xdf=application/xcap-diff+xml 973 | .xdm=application/vnd.syncml.dm+xml 974 | .xdp=application/vnd.adobe.xdp+xml 975 | .xdssc=application/dssc+xml 976 | .xdw=application/vnd.fujixerox.docuworks 977 | .xenc=application/xenc+xml 978 | .xer=application/patch-ops-error+xml 979 | .xfdf=application/vnd.adobe.xfdf 980 | .xfdl=application/vnd.xfdl 981 | .xht=application/xhtml+xml 982 | .xhtml=application/xhtml+xml 983 | .xhvml=application/xv+xml 984 | .xif=image/vnd.xiff 985 | .xla=application/vnd.ms-excel 986 | .xlam=application/vnd.ms-excel.addin.macroenabled.12 987 | .xlc=application/vnd.ms-excel 988 | .xlf=application/x-xliff+xml 989 | .xlm=application/vnd.ms-excel 990 | .xls=application/vnd.ms-excel 991 | .xlsb=application/vnd.ms-excel.sheet.binary.macroenabled.12 992 | .xlsm=application/vnd.ms-excel.sheet.macroenabled.12 993 | .xlsx=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 994 | .xlt=application/vnd.ms-excel 995 | .xltm=application/vnd.ms-excel.template.macroenabled.12 996 | .xltx=application/vnd.openxmlformats-officedocument.spreadsheetml.template 997 | .xlw=application/vnd.ms-excel 998 | .xm=audio/xm 999 | .xml=application/xml 1000 | .xo=application/vnd.olpc-sugar 1001 | .xop=application/xop+xml 1002 | .xpi=application/x-xpinstall 1003 | .xpl=application/xproc+xml 1004 | .xpm=image/x-xpixmap 1005 | .xpr=application/vnd.is-xpr 1006 | .xps=application/vnd.ms-xpsdocument 1007 | .xpw=application/vnd.intercon.formnet 1008 | .xpx=application/vnd.intercon.formnet 1009 | .xsl=application/xml 1010 | .xslt=application/xslt+xml 1011 | .xsm=application/vnd.syncml+xml 1012 | .xspf=application/xspf+xml 1013 | .xul=application/vnd.mozilla.xul+xml 1014 | .xvm=application/xv+xml 1015 | .xvml=application/xv+xml 1016 | .xwd=image/x-xwindowdump 1017 | .xyz=chemical/x-xyz 1018 | .xz=application/x-xz 1019 | .yang=application/yang 1020 | .yin=application/yin+xml 1021 | .z=application/x-compress 1022 | .Z=application/x-compress 1023 | .z1=application/x-zmachine 1024 | .z2=application/x-zmachine 1025 | .z3=application/x-zmachine 1026 | .z4=application/x-zmachine 1027 | .z5=application/x-zmachine 1028 | .z6=application/x-zmachine 1029 | .z7=application/x-zmachine 1030 | .z8=application/x-zmachine 1031 | .zaz=application/vnd.zzazz.deck+xml 1032 | .zip=application/zip 1033 | .zir=application/vnd.zul 1034 | .zirz=application/vnd.zul 1035 | .zmm=application/vnd.handheld-entertainment+xml 1036 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/TestServer.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server; 2 | 3 | import com.ljm.server.config.ServerConfig; 4 | import org.junit.BeforeClass; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertTrue; 8 | 9 | /** 10 | * @author 李佳明 https://github.com/pkpk1234 11 | * @date 2018-01-2018/1/4 12 | */ 13 | public class TestServer extends TestServerBase { 14 | private static Server server; 15 | 16 | @BeforeClass 17 | public static void init() { 18 | 19 | ServerConfig serverConfig = ServerConfig.builder().build(); 20 | server = ServerFactory.getServer(serverConfig); 21 | } 22 | 23 | @Test 24 | public void testServerStart() { 25 | startServer(server); 26 | waitServerStart(server); 27 | assertTrue("服务器启动后,状态是STARTED", server.getStatus().equals(ServerStatus.STARTED)); 28 | } 29 | 30 | @Test 31 | public void testServerStop() { 32 | server.stop(); 33 | assertTrue("服务器关闭后,状态是STOPED", server.getStatus().equals(ServerStatus.STOPED)); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/TestServerAcceptRequest.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server; 2 | 3 | import com.ljm.server.config.ServerConfig; 4 | import com.ljm.server.demo.EchoEventHandler; 5 | import com.ljm.server.event.listener.EventListener; 6 | import com.ljm.server.io.connection.Connection; 7 | import com.ljm.server.io.connector.impl.socket.SocketConnector; 8 | import com.ljm.server.io.connector.impl.socket.SocketConnectorFactory; 9 | import com.ljm.server.io.event.listener.impl.ConnectionEventListener; 10 | import com.ljm.server.io.utils.IoUtils; 11 | import org.junit.AfterClass; 12 | import org.junit.BeforeClass; 13 | import org.junit.Test; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.io.IOException; 18 | import java.net.InetSocketAddress; 19 | import java.net.Socket; 20 | import java.net.SocketAddress; 21 | 22 | import static org.junit.Assert.assertTrue; 23 | 24 | /** 25 | * @author 李佳明 https://github.com/pkpk1234 26 | * @date 2018-01-2018/1/5 27 | */ 28 | public class TestServerAcceptRequest extends TestServerBase { 29 | private static Logger logger = LoggerFactory.getLogger(TestServerAcceptRequest.class); 30 | private static Server server; 31 | // 设置超时时间为500毫秒 32 | private static final int TIMEOUT = 500; 33 | 34 | @BeforeClass 35 | public static void init() { 36 | EventListener socketEventListener = 37 | new ConnectionEventListener(new EchoEventHandler()); 38 | SocketConnector connector = SocketConnectorFactory.build(ServerConfig.DEFAULT_PORT, socketEventListener); 39 | ServerConfig serverConfig = ServerConfig.builder().addConnector(connector).build(); 40 | server = ServerFactory.getServer(serverConfig); 41 | } 42 | 43 | @Test 44 | public void testServerAcceptRequest() { 45 | // 如果server没有启动,首先启动server 46 | if (server.getStatus().equals(ServerStatus.STOPED)) { 47 | startServer(server); 48 | waitServerStart(server); 49 | Socket socket = new Socket(); 50 | SocketAddress endpoint = new InetSocketAddress("localhost", 51 | ServerConfig.DEFAULT_PORT); 52 | try { 53 | // 试图发送请求到服务器,超时时间为TIMEOUT 54 | socket.connect(endpoint, TIMEOUT); 55 | assertTrue("服务器启动后,能接受请求", socket.isConnected()); 56 | } catch (IOException e) { 57 | logger.error(e.getMessage(), e); 58 | } finally { 59 | IoUtils.closeQuietly(socket); 60 | } 61 | } 62 | } 63 | 64 | @AfterClass 65 | public static void destroy() { 66 | server.stop(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/TestServerBase.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * @author 李佳明 https://github.com/pkpk1234 10 | * @date 2018-01-2018/1/6 11 | */ 12 | public abstract class TestServerBase { 13 | private static Logger logger = LoggerFactory.getLogger(TestServerBase.class); 14 | 15 | /** 16 | * 在单独的线程中启动Server,如果启动不成功,抛出异常 17 | * 18 | * @param server 19 | */ 20 | protected void startServer(Server server) { 21 | //在另外一个线程中启动server 22 | new Thread(() -> { 23 | try { 24 | server.start(); 25 | } catch (IOException e) { 26 | //转为RuntimeException抛出,避免异常丢失 27 | throw new RuntimeException(e); 28 | } 29 | }).start(); 30 | } 31 | 32 | /** 33 | * 等待Server启动 34 | * 35 | * @param server 36 | */ 37 | protected void waitServerStart(Server server) { 38 | //如果server未启动,就sleep一下 39 | while (server.getStatus().equals(ServerStatus.STOPED)) { 40 | logger.info("等待server启动"); 41 | try { 42 | Thread.sleep(500); 43 | } catch (InterruptedException e) { 44 | logger.error(e.getMessage(), e); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/TestServerSuite.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.junit.runners.Suite; 5 | 6 | /** 7 | * @author 李佳明 https://github.com/pkpk1234 8 | * @date 2018-01-2018/1/9 9 | */ 10 | @RunWith(Suite.class) 11 | @Suite.SuiteClasses({ 12 | TestServer.class, 13 | TestServerAcceptRequest.class 14 | }) 15 | public class TestServerSuite { 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/com/ljm/server/protocol/http/body/TestHttpBodyInputStream.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.com.ljm.server.protocol.http.body; 2 | 3 | import com.ljm.server.protocol.http.body.HttpBodyInputStream; 4 | import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream; 5 | import org.apache.commons.io.IOUtils; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.ByteArrayInputStream; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | 17 | /** 18 | * @author 李佳明 https://github.com/pkpk1234 19 | * @date 2018-01-2018/1/30 20 | */ 21 | public class TestHttpBodyInputStream { 22 | private static final Logger LOGGER = 23 | LoggerFactory.getLogger(TestHttpBodyInputStream.class); 24 | /* 25 | 7 26 | Mozilla 27 | 9 28 | Developer 29 | 7 30 | Network 31 | 0 32 | 33 | */ 34 | private static final String TEST_OTHER_MESSAGE = "testtest"; 35 | private static final String CHUNKED_BODY = "7\r\n" + 36 | "Mozilla\r\n" + 37 | "9\r\n" + 38 | "Developer\r\n" + 39 | "7\r\n" + 40 | "Network\r\n" + 41 | "0\r\n" + 42 | "\r\n"; 43 | 44 | private static final String BODY = CHUNKED_BODY + TEST_OTHER_MESSAGE; 45 | 46 | private static final String NORMAL_BODY = "Mozilla Developer Network"; 47 | 48 | @Test 49 | public void testReadChunkedBody() throws IOException { 50 | InputStream in = new ByteArrayInputStream(BODY.getBytes()); 51 | HttpBodyInputStream httpBodyInputStream = new HttpBodyInputStream(in, true); 52 | ByteOutputStream out = new ByteOutputStream(); 53 | IOUtils.copy(httpBodyInputStream, out); 54 | LOGGER.info(out.toString()); 55 | assertEquals(CHUNKED_BODY, out.toString()); 56 | } 57 | 58 | @Test 59 | public void testReadBody() throws IOException { 60 | InputStream in = new ByteArrayInputStream(NORMAL_BODY.getBytes()); 61 | HttpBodyInputStream httpBodyInputStream = new HttpBodyInputStream(in, false); 62 | httpBodyInputStream.setContentLength(25); 63 | ByteOutputStream out = new ByteOutputStream(); 64 | IOUtils.copy(httpBodyInputStream, out); 65 | LOGGER.info(out.toString()); 66 | assertEquals(NORMAL_BODY, out.toString()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/com/ljm/server/protocol/http/response/TestHttpResponseMessageBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.com.ljm.server.protocol.http.response; 2 | 3 | import com.ljm.server.protocol.http.HttpResponseMessage; 4 | import com.ljm.server.protocol.http.ResponseLine; 5 | import com.ljm.server.protocol.http.header.HttpHeader; 6 | import com.ljm.server.protocol.http.header.HttpMessageHeaders; 7 | import com.ljm.server.protocol.http.header.IMessageHeaders; 8 | import com.ljm.server.protocol.http.response.HttpResponseMessageBuilder; 9 | import org.junit.Test; 10 | import sun.net.www.MimeTable; 11 | 12 | import javax.activation.MimetypesFileTypeMap; 13 | 14 | import java.io.IOException; 15 | import java.nio.file.Files; 16 | import java.nio.file.Path; 17 | import java.nio.file.Paths; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNotNull; 21 | 22 | /** 23 | * @author 李佳明 https://github.com/pkpk1234 24 | * @date 2018/1/22 25 | */ 26 | public class TestHttpResponseMessageBuilder { 27 | @Test 28 | public void test() { 29 | IMessageHeaders messageHeaders = new HttpMessageHeaders(); 30 | messageHeaders.addHeader(new HttpHeader("Content-Type", "plain/html")); 31 | HttpResponseMessage result = HttpResponseMessageBuilder.builder() 32 | .withResponseLine(new ResponseLine("HTTP/1.1", 200, "OK")) 33 | .withMessageHeaders(messageHeaders).build(); 34 | assertNotNull(result); 35 | assertEquals("plain/html", 36 | result.getMessageHeaders().getFirstHeader("Content-Type").getValue()); 37 | } 38 | 39 | @Test 40 | public void test2() throws IOException { 41 | System.out.println(Files.probeContentType(Paths.get("", ".js"))); 42 | System.out.println(Files.probeContentType(Paths.get("", ".html"))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/io/connection/socket/TestSocketConnection.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.io.connection.socket; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.net.Socket; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | import static org.mockito.Mockito.mock; 12 | import static org.mockito.Mockito.when; 13 | 14 | public class TestSocketConnection { 15 | 16 | 17 | @Test 18 | public void testRead() throws IOException { 19 | Socket socket = mock(Socket.class); 20 | when(socket.getInputStream()).thenReturn(new ByteArrayInputStream("Hello world".getBytes())); 21 | when(socket.getOutputStream()).thenReturn(new ByteArrayOutputStream(64)); 22 | 23 | SocketConnection socketConnection = new SocketConnection(socket); 24 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(64); 25 | byte[] bytes = new byte[64]; 26 | int readLength = -1; 27 | while ((readLength = socketConnection.read(bytes, 0, bytes.length)) != -1) { 28 | outputStream.write(bytes, 0, readLength); 29 | } 30 | assertEquals("Hello world", outputStream.toString("UTF-8")); 31 | } 32 | 33 | @Test 34 | public void testWrite() throws IOException { 35 | Socket socket = mock(Socket.class); 36 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(64); 37 | when(socket.getOutputStream()).thenReturn(byteArrayOutputStream); 38 | 39 | SocketConnection socketConnection = new SocketConnection(socket); 40 | String input = "Hello world"; 41 | socketConnection.write(input.getBytes()); 42 | assertEquals(input, byteArrayOutputStream.toString("UTF-8")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/protocol/http/parser/TestDefaultHttpBodyParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.body.HttpBody; 4 | import org.apache.commons.io.IOUtils; 5 | import org.junit.Test; 6 | 7 | import java.io.ByteArrayInputStream; 8 | import java.io.IOException; 9 | import java.io.UnsupportedEncodingException; 10 | 11 | import static org.junit.Assert.assertArrayEquals; 12 | 13 | /** 14 | * @author 李佳明 https://github.com/pkpk1234 15 | * @date 2018-01-2018/1/21 16 | */ 17 | public class TestDefaultHttpBodyParser { 18 | 19 | private static final String BODY = "name=Professional%20Ajax&publisher=Wiley\r\n"; 20 | 21 | @Test 22 | public void test() throws IOException { 23 | byte[] bytes = BODY.getBytes("utf-8"); 24 | HttpParserContext.setInputStream( 25 | new ByteArrayInputStream(bytes)); 26 | HttpParserContext.setBodyInfo( 27 | new BodyInfo("", "", true, bytes.length)); 28 | DefaultHttpBodyParser httpBodyParser 29 | = new DefaultHttpBodyParser(); 30 | HttpBody httpBody = httpBodyParser.parse(); 31 | byte[] bodyContent = 32 | IOUtils.readFully(httpBody.getInputStream(), (int) httpBody.getContentLength()); 33 | assertArrayEquals(BODY.getBytes(), bodyContent); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/protocol/http/parser/TestDefaultHttpHeaderParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.header.HttpMessageHeaders; 4 | import org.junit.Test; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.UnsupportedEncodingException; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | import static org.junit.Assert.assertTrue; 14 | 15 | /** 16 | * @author 李佳明 https://github.com/pkpk1234 17 | * @date 2018-01-2018/1/16 18 | */ 19 | public class TestDefaultHttpHeaderParser { 20 | private static final Logger LOGGER = LoggerFactory.getLogger(TestDefaultHttpHeaderParser.class); 21 | private static final String HTTP_MESSAGE = 22 | "POST / HTTP1.1\r\n" + 23 | "Host:www.wrox.com\r\n" + 24 | "User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)\r\n" + 25 | "Content-Type:application/x-www-form-urlencoded\r\n" + 26 | "Content-Length:40\r\n" + 27 | "Connection: Keep-Alive\r\n" + 28 | "\r\n" + 29 | "name=Professional%20Ajax&publisher=Wiley\r\n"; 30 | 31 | @Test 32 | public void testRegex() { 33 | String regex = "(?m)^\r\n"; 34 | Pattern pattern = Pattern.compile(regex); 35 | Matcher matcher = pattern.matcher(HTTP_MESSAGE); 36 | if (matcher.find()) { 37 | int end = matcher.end(); 38 | LOGGER.info("" + end); 39 | LOGGER.info("rest {} ----", HTTP_MESSAGE.substring(0, end)); 40 | } 41 | 42 | } 43 | 44 | @Test 45 | public void test() throws UnsupportedEncodingException { 46 | HttpParserContext context = new HttpParserContext(); 47 | DefaultHttpHeaderParser httpHeaderParser = new DefaultHttpHeaderParser(); 48 | context.setHttpMessageBytes(HTTP_MESSAGE.getBytes("utf-8")); 49 | HttpMessageHeaders httpHeaders = httpHeaderParser.parse(); 50 | assertTrue(httpHeaders.hasHeader("Host")); 51 | assertEquals("40", httpHeaders.getFirstHeader("Content-Length").getValue()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/protocol/http/parser/TestDefaultHttpQueryParameterParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.HttpQueryParameter; 4 | import com.ljm.server.protocol.http.HttpQueryParameters; 5 | import org.junit.Test; 6 | 7 | import java.util.List; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertNotNull; 11 | 12 | /** 13 | * @author 李佳明 https://github.com/pkpk1234 14 | * @date 2018-01-2018/1/15 15 | */ 16 | public class TestDefaultHttpQueryParameterParser { 17 | 18 | @Test 19 | public void test() { 20 | String queryStr = "a=123&a1=1&b=456&a=321"; 21 | HttpParserContext.setRequestQueryString(queryStr); 22 | DefaultHttpQueryParameterParser httpRequestParameterParser 23 | = new DefaultHttpQueryParameterParser(); 24 | HttpQueryParameters result = httpRequestParameterParser.parse(); 25 | List parameters = result.getQueryParameter("a"); 26 | assertNotNull(parameters); 27 | assertEquals(2, parameters.size()); 28 | assertEquals("123", parameters.get(0).getValue()); 29 | assertEquals("321", parameters.get(1).getValue()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/protocol/http/parser/TestDefaultHttpRequestLineParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.RequestLine; 4 | import org.junit.Test; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.net.URI; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * @author 李佳明 https://github.com/pkpk1234 14 | * @date 2018-01-2018/1/14 15 | */ 16 | public class TestDefaultHttpRequestLineParser { 17 | private static final Logger 18 | LOGGER = LoggerFactory.getLogger(TestDefaultHttpRequestLineParser.class); 19 | 20 | @Test 21 | public void test() { 22 | HttpParserContext parserContext = new HttpParserContext(); 23 | parserContext.setHttpMessageBytes("GET /hello.txt HTTP/1.1\r\n".getBytes()); 24 | DefaultHttpRequestLineParser defaultRequestLineParser 25 | = new DefaultHttpRequestLineParser(); 26 | 27 | RequestLine result = defaultRequestLineParser.parse(); 28 | String method = result.getMethod(); 29 | assertEquals("GET", method); 30 | final URI requestURI = result.getRequestURI(); 31 | assertEquals(URI.create("/hello.txt"), requestURI); 32 | LOGGER.info(requestURI.getQuery()); 33 | LOGGER.info(requestURI.getFragment()); 34 | assertEquals("HTTP/1.1", result.getHttpVersion()); 35 | assertEquals(requestURI.getQuery(), parserContext.getRequestQueryString()); 36 | } 37 | 38 | @Test 39 | public void testQuery() { 40 | HttpParserContext parserContext = new HttpParserContext(); 41 | parserContext.setHttpMessageBytes("GET /test?a=123&a1=1&b=456 HTTP/1.1\r\n".getBytes()); 42 | 43 | DefaultHttpRequestLineParser defaultRequestLineParser 44 | = new DefaultHttpRequestLineParser(); 45 | RequestLine result = defaultRequestLineParser.parse(); 46 | String method = result.getMethod(); 47 | assertEquals("GET", method); 48 | final URI requestURI = result.getRequestURI(); 49 | assertEquals(URI.create("/test?a=123&a1=1&b=456"), requestURI); 50 | LOGGER.info(requestURI.getQuery()); 51 | assertEquals("HTTP/1.1", result.getHttpVersion()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/ljm/server/protocol/http/parser/TestDefaultHttpRequestMessageParser.java: -------------------------------------------------------------------------------- 1 | package com.ljm.server.protocol.http.parser; 2 | 3 | import com.ljm.server.protocol.http.HttpMessage; 4 | import com.ljm.server.protocol.http.HttpQueryParameters; 5 | import com.ljm.server.protocol.http.HttpRequestMessage; 6 | import com.ljm.server.protocol.http.RequestLine; 7 | import com.ljm.server.protocol.http.body.HttpBody; 8 | import com.ljm.server.protocol.http.header.IMessageHeaders; 9 | import org.apache.commons.io.IOUtils; 10 | import org.junit.Test; 11 | 12 | import java.io.ByteArrayInputStream; 13 | import java.io.IOException; 14 | import java.util.Optional; 15 | 16 | import static org.junit.Assert.*; 17 | 18 | /** 19 | * @author 李佳明 https://github.com/pkpk1234 20 | * @date 2018-01-2018/1/21 21 | */ 22 | public class TestDefaultHttpRequestMessageParser { 23 | private static final String MESSAGE_NO_Body = 24 | "POST / HTTP/1.1\r\n" + 25 | "Host:www.wrox.com\r\n" + 26 | "User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)\r\n" + 27 | "Content-Type:application/x-www-form-urlencoded\r\n" + 28 | "Content-Length:40\r\n" + 29 | "Connection: Keep-Alive\r\n" + 30 | "\r\n"; 31 | private static final String BODY = "name=Professional%20Ajax&publisher=Wiley"; 32 | private static final String HTTP_MESSAGE = MESSAGE_NO_Body + BODY; 33 | 34 | @Test 35 | public void test() throws IOException { 36 | DefaultHttpRequestMessageParser httpRequestMessageParser 37 | = new DefaultHttpRequestMessageParser(new DefaultHttpRequestLineParser(), 38 | new DefaultHttpQueryParameterParser(), 39 | new DefaultHttpHeaderParser(), 40 | new DefaultHttpBodyParser()); 41 | HttpMessage result 42 | = httpRequestMessageParser.parse(new ByteArrayInputStream(HTTP_MESSAGE.getBytes())); 43 | assertEquals("HTTP/1.1", result.getStartLine().getHttpVersion()); 44 | HttpRequestMessage requestMessage 45 | = HttpRequestMessage.class.cast(result); 46 | RequestLine requestLine = requestMessage.getRequestLine(); 47 | assertEquals("POST", requestLine.getMethod()); 48 | HttpQueryParameters queryParameters = requestMessage.getHttpQueryParameters(); 49 | queryParameters.hasRequestParameter("123"); 50 | 51 | IMessageHeaders httpHeaders = requestMessage.getMessageHeaders(); 52 | assertEquals(5, httpHeaders.getAllHeaders().size()); 53 | assertEquals("Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)" 54 | , httpHeaders.getFirstHeader("User-Agent").getValue()); 55 | 56 | Optional opBody = requestMessage.getHttpBody(); 57 | assertTrue(opBody.isPresent()); 58 | HttpBody httpBody = opBody.get(); 59 | byte[] content = IOUtils.readFully(httpBody.getInputStream(), (int) opBody.get().getContentLength()); 60 | assertArrayEquals(BODY.getBytes(), content); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | front.log 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Beggar Servlet Container 6 | 7 | 8 | 9 | 10 |

11 | 12 | 13 | -------------------------------------------------------------------------------- /web/index.js: -------------------------------------------------------------------------------- 1 | var date = new Date(); 2 | var content = "now is " + date; 3 | var div = document.querySelector("#content"); 4 | div.innerHTML = content; -------------------------------------------------------------------------------- /web/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: antiquewhite; 3 | } 4 | 5 | #content { 6 | background-color: cornflowerblue; 7 | } -------------------------------------------------------------------------------- /web/spring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkpk1234/BeggarServletContainer/cfd2d32cc494beedeb74e357e7d4ce37c84e2904/web/spring.jpg --------------------------------------------------------------------------------