基于Java BIO,手写一个Web Server。Github 地址: https://github.com/xiaxinyu/summer_projects/tree/master/project-web-server
一、启动
1、启动类
package com.web.server.boot;
import java.util.Map;
import com.web.server.core.Servlet;
import com.web.server.core.WebServer;
import com.web.server.core.configuration.ConfigurationFactory;
public class Bootstrap {
public static void main(String[] args) {
Map configuration = ConfigurationFactory.getConfiguration();
new WebServer().start(configuration);
}
}
2、配置解析类
package com.web.server.core.configuration;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.web.server.core.Servlet;
import com.web.server.exception.WebServerException;
public class ConfigurationFactory {
private final static Logger logger = LoggerFactory.getLogger(ConfigurationFactory.class);
private static Properties properties = new Properties();
public static Map getConfiguration() {
Map configuration = null;
InputStream inputStream = null;
try {
String basePath = ConfigurationFactory.class.getResource("/").getPath();
inputStream = new FileInputStream(basePath + "web.properties");
properties.load(inputStream);
Set
二、WebSever 核心服务器类
package com.web.server.core;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebServer {
private final static Logger logger = LoggerFactory.getLogger(WebServer.class);
public static final int PORT = 8080;
public static final int BACK_LOG = 50;
public void start(Map configuration) {
ServerSocket server = null;
try {
server = new ServerSocket();
server.bind(new InetSocketAddress(PORT), BACK_LOG);
logger.info("Start server, listen port {}", PORT);
while (true) {
new ConnectionProcessor(server.accept(), configuration).start();
}
} catch (IOException e) {
logger.error("Startring server has error", e);
System.exit(1);
} finally {
if (server != null) {
try {
server.close();
} catch (IOException e) {
logger.error("Startring server has error", e);
}
}
}
}
}
三、Connection 核心服务器类
package com.web.server.core;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.web.server.core.enumeration.HttpStatus;
public class ConnectionProcessor extends Thread {
private final static Logger logger = LoggerFactory.getLogger(ConnectionProcessor.class);
private Socket client;
private Map servletMapping;
public ConnectionProcessor(Socket client, Map servletMapping) {
this.client = client;
this.servletMapping = servletMapping;
}
@Override
public void run() {
logger.info("Start-New connection:" + client.getInetAddress() + ":" + client.getPort());
process(client);
}
private void process(Socket client) {
InputStream is = null;
OutputStream os = null;
try {
is = client.getInputStream();
os = client.getOutputStream();
Request request = new Request(is);
logger.info(request.toString());
Response response = new Response(os);
Servlet servlet = servletMapping.get(request.getUrl());
if (null != servlet) {
servlet.service(request, response);
} else {
logger.warn("Can't found {}", request.getUrl());
response.setStatus(HttpStatus.S_404);
}
response.flush();
os.flush();
} catch (Exception e) {
logger.error("Process client request has error.", e);
} finally {
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
if (client != null) {
client.close();
}
} catch (IOException e) {
logger.error("Process client request has error.", e);
}
}
}
}
四、模版模式Servlet处理类
package com.web.server.core;
import com.web.server.core.enumeration.MethodType;
import com.web.server.exception.NotSupportMethodTypeException;
public abstract class Servlet {
public void service(Request request, Response response) throws Exception {
if (MethodType.GET == request.getMethod()) {
doGet(request, response);
} else if (MethodType.POST == request.getMethod()) {
doPost(request, response);
} else {
throw new NotSupportMethodTypeException(request.getMethod() + " can't be support.");
}
}
public abstract void doGet(Request request, Response response) throws Exception;
public abstract void doPost(Request request, Response response) throws Exception;
}
五、请求处理类
package com.web.server.core;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.web.server.core.enumeration.MethodType;
import com.web.server.exception.HttpProtocolParsingException;
public class Request {
private final static Logger logger = LoggerFactory.getLogger(Request.class);
public static final int SMALL_BUF_SIZE = 1024;
private String[] httpProtocolLines;
private String httpProtocol;
private String url;
private MethodType method;
private Map parameter = new HashMap<>();
public Request(InputStream is) {
try {
this.httpProtocol = getHttpProtocol(is);
parseHttpProtocol(this.httpProtocol);
} catch (IOException e) {
logger.error("Parse http protocol has error.", e);
}
}
private String getHttpProtocol(InputStream is) throws IOException {
String httpProtocol = null;
byte[] buffer = new byte[SMALL_BUF_SIZE];
int len = 0;
if ((len = is.read(buffer)) > 0) {
httpProtocol = new String(buffer, 0, len);
}
return httpProtocol;
}
private void parseHttpProtocol(String httpProtocol) {
httpProtocolLines = httpProtocol.split("\\n");
if (httpProtocol.length() > 0) {
parseBaseProtocolLine(httpProtocolLines[0]);
} else {
throw new HttpProtocolParsingException("Http protocol has error.");
}
}
private void parseBaseProtocolLine(String protocolLine) {
String[] segments = protocolLine.split("\\s");
// Parse method
MethodType methodType = MethodType.of(segments[0]);
if (null != methodType) {
this.method = methodType;
} else {
throw new HttpProtocolParsingException("Can't recongnize method type.");
}
String[] items = segments[1].split("\\?");
this.url = items[0];
if (items.length <= 1) {
return;
}
// Parse parameters
String[] params = items[1].split("\\&");
if (params != null && params.length > 0) {
for (String param : params) {
String[] pairs = param.split("\\=");
this.parameter.put(pairs[0], pairs[1]);
}
}
}
public String getUrl() {
return this.url;
}
public MethodType getMethod() {
return this.method;
}
public String getHttpProtocol() {
return httpProtocol;
}
public Map getParameter() {
return this.parameter;
}
@Override
public String toString() {
return "Request [url=" + url + ", method=" + method + "]";
}
}
六、响应处理类
package com.web.server.core;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.web.server.core.enumeration.HttpStatus;
import com.web.server.core.enumeration.HttpStatusText;
import com.web.server.utils.ResponseHelper;
public class Response {
private OutputStream os;
private HttpStatus status = HttpStatus.S_200;
private List responseTexts = new ArrayList<>();
public Response(OutputStream os) {
this.os = os;
}
public HttpStatus getStatus() {
return status;
}
public void setStatus(HttpStatus status) {
this.status = status;
}
public void write(String outContext) {
responseTexts.add(outContext);
}
public void flush() throws IOException {
if (HttpStatus.S_200 == status) {
String responseText = ResponseHelper.fmtResponseText(responseTexts);
if (StringUtils.isBlank(responseText)) {
responseText = HttpStatusText.of(HttpStatus.S_200).getText();
}
os.write(ResponseHelper.fmtResponse(status, responseText).getBytes());
} else {
os.write(ResponseHelper.fmtResponse(status, HttpStatusText.of(status).getText()).getBytes());
}
responseTexts.clear();
}
}