手写静态服务器,其实挺简单的
思路
1 写服务器之前先了解一下,服务器和浏览器之间的交互过程。
当你输入网址回车时,浏览器向目标服务器发送请求。服务器解析url,将你想要的资源发送给浏览器(响应)。
2 了解交互过程,我们知道服务器主要做两个事
1)监听端口,获取请求(说白了就是服务器想知道你要我给你那些东西)
2)将浏览器想要的东西拼成浏览器看的懂得报文。
扫一下盲(报文是什么样的)
知识点,小结(坑)
踩过许多坑,先来说说坑吧。
坑1:中文字符乱码问题
(1)读取字节不完整
utp-8中,一个英文字符占一个字节,中文字符占三个字节
下图就是浏览器读取不完整造成的
解决方案:设置请求头中contextlength的值时,不能使用 data.length(),应使用data.getBytes().length。 (2)字符集不统一
在中国的电脑黑窗口字符集是gbk,而项目是utf-8
解决方案:如图坑2 在拼和拆报文时一定要仔细,个人建议在纸上画图好理解清楚不宜错
我本人就是在草稿纸上画了图。
敲完代码,有写一遍注释,在写这篇文章。实在想不起但是的坑了。可能我已经完全掌握了,O(∩_∩)O哈哈~
源码(注释很详细)
应该没用谁的源码写注释比我的清楚了吧
主类
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author mzy
* @date 2021/3/24
*/
public class Http {
public static void main(String[] args) throws Exception {
//创建服务器
ServerSocket server = new ServerSocket();
//绑定端口
server.bind(new InetSocketAddress(InetAddress.getByName("192.168.1.200"),4888));
System.out.println("the server is running , listening port is 4888");
while(true){
//监听端口
Socket accept = server.accept();
System.out.println("a penson"+accept.getRemoteSocketAddress().toString() +" is comming");
// new MyTask(accept).start();
//使用线程池
ExecutorService executorService = Executors.newFixedThreadPool(20);
executorService.submit(new MyTask(accept));
}
}
}
线程类
// An highlighted block
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @author mzy
* @date 2021/3/25
*/
public class MyTask implements Runnable{
private Socket accept =null;
public MyTask(Socket accept){
this.accept=accept;
}
@Override
public void run() {
InputStream inputStream =null;
OutputStream out = null;
try{
StringBuilder sb = new StringBuilder();
//通过监听获取输入流,将内容读入StringBuilder中
inputStream= accept.getInputStream();
byte[] bytes = new byte[512];
int len;
while((len = inputStream.read(bytes))!=-1){
sb.append(new String(bytes,0,len));
if(len<512){
accept.shutdownInput();
}
}
//封装请求 :将请求的报文分解成requset对象的属性。
//使用静态方法
Requset requset = Requset.buildRequset(sb.toString());
//获取输出流
out = accept.getOutputStream();
//封装响应:1拼接报文 2报文为response的属性
Response response = new Response();
response.setData(HttpUtils.getPage(requset.getUrl()));
//封装的好处,可以随意添加响应头。
response.addHeader("a","b");
//这里write方法是response的方法:用输入流将响应的内容写出去
response.write(out);
//用完了,要养成关的习惯。
}catch(Exception e){
e.printStackTrace();
}finally{
if(inputStream !=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out !=null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (accept!=null){
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
请求类
// An highlighted block
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* @author mzy
* @date 2021/3/25
*/
public class Requset {
private String type;
private String url;
private String protocol;
private String contentType;
//只有new 实例化才能调方法
private Map<String,String> hraders = new HashMap<>(8);
private Map<String,String> attributes =new HashMap<>(8);
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public Map<String, String> getHraders() {
return hraders;
}
public void setHraders(Map<String, String> hraders) {
this.hraders = hraders;
}
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
@Override
public String toString() {
return "Requset{" +
"type='" + type + '\'' +
", url='" + url + '\'' +
", protocol='" + protocol + '\'' +
", contentType='" + contentType + '\'' +
", hraders=" + hraders +
", attributes=" + attributes +
'}';
}
public static Requset buildRequset(String requseturl){
Requset requset = new Requset();
//拆解请求报文,实例化对象
String[] split = requseturl.split("\r\n\r\n");
// 请求行 和 请求头
String[] lineAndHeader = split[0].split("\r\n");
String[] lines = lineAndHeader[0].split(" ");
requset.setType(lines[0]);
requset.setUrl(lines[1]);
requset.setProtocol(lines[2]);
for(int i=1;i<lineAndHeader.length;i++){
String[] header =lineAndHeader[i].split(": ");
requset.getHraders().put(header[0].trim().toLowerCase(),header[1].trim().toLowerCase());
}
//请求体
if(split.length == 2){
}
return requset;
}
}
响应类
// An highlighted block
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* @author mzy
* @date 2021/3/25
*/
public class Response {
public Response(String protocol, Integer code, String msg) {
this.protocol=protocol;
this.code = code;
this.msg = msg;
}
public Response(){
}
private String protocol="HTTP/1.1";
private Integer code=200;
private String data;
private String msg="OK";
private String ContentType="text/html;charset=utf-8";
private String ContentLength;
private Map<String,String> headers=new HashMap(){{
put("content-type",ContentType);
}};
public String getProtocol() {
return protocol;
}
public String getContentType() {
return this.getHeaders().get("content-type");
}
public void setContentType(String contentType) {
this.getHeaders().put("content-type",contentType);
}
public String getContentLength() {
return this.getHeaders().get("content-length");
}
public void setContentLength(String contentLength) {
this.getHeaders().put("content-length",this.data.getBytes().length+"");
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
this.setContentLength(data.getBytes().length+"");
}
public void addHeader(String key ,String value){
this.headers.put(key,value);
}
//拼报文
public String bulidResponse(){
StringBuilder sb = new StringBuilder();
sb.append(this.getProtocol()).append(" ")
.append(this.getCode()).append(" ")
.append(this.getMsg()).append("\r\n");
for(Map.Entry<String,String> entry : this.getHeaders().entrySet()){
sb.append(entry.getKey()).append(": ")
.append(entry.getValue()).append("\r\n");
}
sb.append("\r\n").append(this.getData());
return sb.toString();
}
//写出去,发给客户端。
public void write(OutputStream os){
try{
os.write(bulidResponse().getBytes());
}catch(IOException e){
e.printStackTrace();
}finally{
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
工具类
// An highlighted block
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* @author mzy
* @date 2021/3/25
*/
public class HttpUtils {
//通过输入一个html文件名 用流 获得它内容的字符串
public static String getPage(String url) {
StringBuilder sb =new StringBuilder();
if("".equals(url) || "/".equals(url) ||url==null){
url="index.html";
}
try{
//获取绝对路径
String path = Test.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = path.substring(0,path.lastIndexOf("/"))+"/pages/";
File file = new File(path + url);
boolean exists = file.exists();
if(!exists){
url="404.html";
}
InputStream resource = new FileInputStream(path+url);
byte[] buf = new byte[512];
int len;
while ((len = resource.read(buf)) != -1) {
sb.append(new String(buf,0,len));
}
}catch(Exception e){
e.printStackTrace();
}
return sb.toString();
}
}
扩展(idea打包 windows命令脚本)
idea打包
这就打包完成了。
创建windows命令脚本
在新建一个记事本,写入在cmd中运行jar包的代码,该文件的后缀为.bat即可
实现的业务展示
1.将写好HTML文件放入pages文件夹中
2.启动服务器,点击xx.bat
小bug(求大牛解决)
这个bug不会影响运行,就是关闭浏览器时会提示错误信息。
需要成品的小伙伴可以私聊
制作不宜,要成品的小伙伴,收取1元的支持费。O(∩_∩)O哈哈~