根据本专题的上一篇文章所说提到的HTTP响应和HTTP请求的格式(HTTP请求和响应格式文章链接)我们可以书写简单的HTTP Server程序,让服务器上的返回给客户端的返回结果返回至网站中
代码如下:
package day0314;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* HTTP服务器(最简单的)
*
* 底层要基于TCP来实现,要按照TCP的基本格式来先进行开发
*/
public class HTTPServerV1 {
private ServerSocket serverSocket = null;
public HTTPServerV1(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
ExecutorService executorService = Executors.newCachedThreadPool();
while (true){
//1.获取连接
Socket clientSocket = serverSocket.accept();
executorService.execute(new Runnable() {
@Override
public void run() {
//2.处理连接(采用短连接)
process(clientSocket);
}
});
}
}
private void process(Socket clientSocket) {
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))){
//下面的操作要严格按照HTTP协议来进行操作
//1.读取请求并解析
// a)解析首行 三部分使用空格切分
String firstLine = bufferedReader.readLine();
String[] firstLineTokens = firstLine.split(" ");//切分
String method = firstLineTokens[0];
String url = firstLineTokens[1];
String version = firstLineTokens[2];
// b)解析header 按行读取,然后按照冒号空格来分割字符
Map<String,String> headers = new HashMap<>();
String line = "";
//readLine()读到的一行内容,时会自动去掉换行符的
while ((line = bufferedReader.readLine()) != null && line.length() !=0){
//还没读完并且读到的不是空字符串
String[] headerTokens = line.split(": ");//用冒号空格来切分
headers.put(headerTokens[0],headerTokens[1]);
}
// c)解析body(暂时先不考虑)
//请求解析完毕,加上一个日志看看请求的内容是否正确
System.out.printf("%s %s %s\n",method,url,version);
for (Map.Entry<String,String> entry : headers.entrySet()){
System.out.print(entry.getKey() + ": " + entry.getValue()+"\n");
}
System.out.println();
//2.根据请求计算响应
//假设不管是啥样的请求,都返回一个hello这样的html
String resp = "";
if(url.equals("/ok")){
bufferedWriter.write(version+" 200 OK\n");
resp = "<h1>hello</h1>";
}else if (url.equals("/notfound")){
bufferedWriter.write(version+" 404 Not Found\n");
resp = "<h1>not found</h1>";
}else if (url.equals("/seeother")){
bufferedWriter.write(version+" 303 See Other\n");
bufferedWriter.write("Location: http://www.baidu.com\n");
}else {
bufferedWriter.write(version+" 200 OK\n");
resp = "<h1>else</h1>";
}
//3.把响应写回客户端
bufferedWriter.write("Content-Type: text/html\n");
bufferedWriter.write("Content-Length: "+resp.getBytes().length+"\n");//此处的长度这样写是得到的字节的数目
//resp.length是字符数目
bufferedWriter.write("\n");
bufferedWriter.write(resp);
bufferedWriter.flush();//刷新
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
clientSocket.close();//短连接,一次交互之后服务器客户端主动断开连接
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
HTTPServerV1 serverV1 = new HTTPServerV1(9090);
serverV1.start();
}
}
运行结果:
浏览器(客户端)中返回结果是:
服务器显示结果是:
抓包结果为:
方法一:
package day0314rev;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpServerV1 {
private ServerSocket serverSocket = null;
public HttpServerV1(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
Socket clientSocket = serverSocket.accept();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
process(clientSocket);
}
});
}
}
private void process(Socket clientSocket) {
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))){
//1.解析获得的请求
//解析首行
String firstLine = bufferedReader.readLine();
String[] firstLineToken = firstLine.split(" ");
String method = firstLineToken[0];
String url = firstLineToken[1];
String version = firstLineToken[2];
Map<String,String> parmenters = new HashMap<>();
int post = url.indexOf("?");
if (post != -1){
//如果url中有?,那么就执行里面的操作
String parmenter = url.substring(post+1);
KVStart(parmenter,parmenters);
}
//解析header
Map<String,String> headers = new HashMap<>();
String line = "";
while ((line = bufferedReader.readLine()) != null && line.length() != 0){
String[] headerToken = line.split(": ");
headers.put(headerToken[0],headerToken[1]);
}
System.out.printf("%s %s %s\n", method,url,version);
for (Map.Entry<String,String> entry : headers.entrySet()){
System.out.print(entry.getKey()+": "+entry.getValue()+"\n");
}
System.out.println();
//2.计算请求
String resp = "";
if (url.equals("/hello")){
bufferedWriter.write(version+" 200 OK\n");
resp = "<h1>hello</h1>";
}else if (url.startsWith("/clar")){
//计算a+b的值
bufferedWriter.write(version+" 200 OK\n");
String aStr = parmenters.get("a");
String bStr = parmenters.get("b");
int a = Integer.parseInt(aStr);
int b = Integer.parseInt(bStr);
int sum = a+b;
resp = "<h1>"+sum+"</h1>";
}else {
bufferedWriter.write(version + " 200 OK\n");
resp = "<h1>defualt</h1>";
}
//3.把请求返回给客户端
bufferedWriter.write("Content-Type: text.html\n");
bufferedWriter.write("Content-Length: "+resp.getBytes().length+"\n");
bufferedWriter.write("\n");
bufferedWriter.write(resp);
bufferedWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void KVStart(String parmenter, Map<String, String> parmenters) {
String[] KVToken = parmenter.split("&");
for (String KV : KVToken){
String[] result = KV.split("=");
parmenters.put(result[0],result[1]);
}
}
public static void main(String[] args) throws IOException {
HttpServerV1 server = new HttpServerV1(9090);
server.start();
}
}
但以上代码在逻辑方面不太清晰,下面用三各类进行整理
第一个类:HttpRequest
package day0314;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
/**
* 请求:表示一个http请求,并负责解析
*/
public class HttpRequest {
private String method;
private String url;
private String version;
private Map<String,String> headers = new HashMap<>();
private Map<String,String> parameters = new HashMap<>();
//请求的构造逻辑,也是用工厂模式来构造
//此处的参数就是从socket中获取到的InputStream对象
//就相当于反序列化
public static HttpRequest build(InputStream inputStream) throws IOException {
HttpRequest request = new HttpRequest();
//此处不能把bufferedReader写道try括号中,一旦写进去就意味着bufferedReader就会被关闭,会影响到clientSocket的状态
//等到最后正给请求处理完了再统一关闭
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//此处的build过程就是解析请求的过程
//1.解析首行
String firstLine = bufferedReader.readLine();
String[] firstLineTokens = firstLine.split(" ");
request.method = firstLineTokens[0];
request.url = firstLineTokens[1];
request.version = firstLineTokens[2];
//2.解析url中的参数
int pos = request.url.indexOf("?");
if (pos != -1){
//pos表示问号的下标
//看看有没有带问号,如果没有带问号就不用解析了
//index.html?a=10&b=20
String parameters = request.url.substring(pos+1);//从i+1开始取
//切分的最终结果
parseKV(parameters,request.parameters);
}
//3.解析header
String line = "";
//readLine()读到的一行内容,时会自动去掉换行符的
while ((line = bufferedReader.readLine()) != null && line.length() !=0){
//还没读完并且读到的不是空字符串
String[] headerTokens = line.split(": ");//用冒号空格来切分
request.headers.put(headerTokens[0],headerTokens[1]);
}
//4.解析body
return request;
}
private static void parseKV(String input, Map<String, String> output) {
//1.先按照&分成若干组键值对
String[] kvTokens = input.split("&");
//2.针对切分结果再分别进行按照=切分,得到了键和值
for (String kv : kvTokens){
String[] result = kv.split("=");
output.put(result[0],result[1]);
}
}
//给这个类构造getter方法,不要搞setter方法,因为请求对象内容应该是从网络上解析来的,用户不应该修改
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
public String getVersion() {
return version;
}
public String getHeaders(String key) {
return headers.get(key);
}
public String getParameters(String key) {
return parameters.get(key);
}
@Override
public String toString() {
return "HttpRequest{" +
"method='" + method + '\'' +
", url='" + url + '\'' +
", version='" + version + '\'' +
", headers=" + headers +
", parameters=" + parameters +
'}';
}
}
第二个类:HttpResponse
package day0314;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;
/**
* 响应:表示一个http响应,负责构造
*/
public class HttpResponse {
private String version = "HTTP/1.1";
private int status; //状态码
private String message; //状态码描述信息
private Map<String,String> headers = new HashMap<>();
private StringBuilder body = new StringBuilder();
//当代码需要把响应写回客户端的时候,就往这个OutputStream里面写就好了
private OutputStream outputStream = null;
//工厂方法
public static HttpResponse build(OutputStream outputStream){
HttpResponse response = new HttpResponse();
response.outputStream = outputStream;
return response;
}
public void setVersion(String version) {
this.version = version;
}
public void setStatus(int status) {
this.status = status;
}
public void setMessage(String message) {
this.message = message;
}
public void setHeaders(String key, String value) {
headers.put(key,value);
}
public void writeBody(String content) {
body.append(content);
}
//以上的设置属性操作都是在内存中搞得,还需要一个专门的方法,把这些属性按照HTTP协议,都写到Socket中
public void flush() throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write(version+" "+status+ " "+message+"\n");
headers.put("Content-Length",body.toString().getBytes().length+"");
for (Map.Entry<String,String> entry : headers.entrySet()){
bufferedWriter.write(entry.getKey()+": "+entry.getValue()+"\n");
}
bufferedWriter.write("\n");
bufferedWriter.write(body.toString());
bufferedWriter.flush();
}
}
第三个类:整理
package day0314;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 主逻辑:HttpRequest和 HttpResponse已经梳理的逻辑,在这里完成调用
*/
public class HttpServerV2 {
private ServerSocket serverSocket = null;
public HttpServerV2(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
ExecutorService executorService = Executors.newCachedThreadPool();
while (true){
Socket clientSocket = serverSocket.accept();
executorService.execute(new Runnable() {
@Override
public void run() {
process(clientSocket);
}
});
}
}
public void process(Socket clientSocket){
try {
//1.读取并解析请求
HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
System.out.println("request:"+request);
HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
response.setHeaders("Content-Type","text.html");
//2.根据请求计算响应
if (request.getUrl().startsWith("/hello")){
response.setStatus(200);
response.setMessage("OK");
response.writeBody("<h1>hello</h1>");
}else if (request.getUrl().startsWith("/calc")){
//根据这个逻辑进行计算
//先获取到a b两个值
String aStr = request.getParameters("a");
String bStr = request.getParameters("b");
int a = Integer.parseInt(aStr);
int b = Integer.parseInt(bStr);
int result = a+b;
response.setStatus(200);
response.setMessage("OK");
response.writeBody("<h1> result = "+result+"</h1>");
} else if (request.getUrl().startsWith("/cookie")){
response.setStatus(200);
response.setMessage("OK");
response.setHeaders("Set-Cookie","I am cookie");
response.writeBody("<h1>set cookie</h1>");
}else{
response.setStatus(200);
response.setMessage("OK");
response.writeBody("<h1>default</h1>");
}
//3.吧响应写回客户端
response.flush();
} catch (IOException | NullPointerException e) {
e.printStackTrace();
}finally {
try {
//这个操作可以同时关闭getInputStream和getOutputStream
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
HttpServerV2 serverV2 = new HttpServerV2(9090);
serverV2.start();
}
}
这样也可以完成上述任务,完成结果如下: