java代码写一个简单的web服务器,思路清晰,代码一看就明白

写一个简单的web服务器

本人出入门槛,做一个小项目分享一下。有什么纰漏还请各位大佬指出。一直在努力,从未停止,大家共同进步。

项目简介

使用B/S架构,简单实现web服务器。本项目未使用任何第三方jar,复制粘贴即可用
浏览器端向服务端发出请求,服务器根据请求的内容,做出相应的响应,返回给浏览器。
eg:浏览器做出了请求:请求一张图片
服务器会在自己的资源文件中查找有没有图片,有的话会返回一个图片。没有的话会做出资源未找到的响应

项目需要用到的主要知识:

​ IO流、网络编程、枚举、HTTP协议、线程池、map集合

项目简单思路:

​ 1.准备一个Request、Response的实体类,

​ 2.准备一个资源文件(source),用来存放浏览器请求的文件

​ 3.写一个工具类(SocketToRequest),把请求到的Socket对象转化为request对象,方便获取请求行、请求头、请求体

​ 4.写一个enum类,因为响应状态的代码是固定的,所以就用枚举把它写死

​ 5.写一个类(FileTypeUtil),来存放响应文件的类型,和字符编码格式

​ 6.因为不知道同时有多少个线程发来,所以这里用一个newCachedThreadPool()的线程池。来响应客户端。

Request实体类

public class Request implements Serializable{
	private static final long serialVersionUID = 1L;
	/*
	 * 请求方式
	 */
	private String requestMethod;
	/*
	 * 请求的协议
	 */
	private String agreeMent = "HTTP/1.1";
	/*
	 * 请求的资源
	 */
	private String requestSource;
	/*
	 * 存放请求头
	 */
	private Map<String, String> map = new HashMap<>();
	/*
	 * 请求体
	 */
	private String requestBody;
	
    public String getAgreeMent() {
		return agreeMent;
	}
	public String getRequestMethod() {
		return requestMethod;
	}
	public void setRequestMethod(String requestMethod) {
		this.requestMethod = requestMethod;
	}
	public String getRequestSource() {
		return requestSource;
	}
	public void setRequestSource(String requestSource) {
		this.requestSource = requestSource;
	}
	public Map<String, String> getMap() {
		return map;
	}
	public void setMap(Map<String, String> map) {
		this.map = map;
	}
	public String getRequestBody() {
		return requestBody;
	}
	public void setRequestBody(String requestBody) {
		this.requestBody = requestBody;
	}
	@Override
	public String toString() {
		return "Request [requestMethod=" + requestMethod + ", agreeMent=" + agreeMent + ", requestSource="
				+ requestSource + ", map=" + map + ", requestBody=" + requestBody + "]";
	}
}

Response实体类

public class Response implements Serializable{
	private static final long serialVersionUID = 1L;
	/*
     *响应行
	 */
	private String agreeMent = "HTTP/1.1";
	/*
     * 响应行状态码
	 */
	private StateCodeEnum stateCode;
	/*
     * 响应头
	 */
	private Map<String, String> map = new HashMap<>();
	
	public String getAgreeMent() {
		return agreeMent;
	}
	public StateCodeEnum getStateCode() {
		return stateCode;
	}
	public void setStateCode(StateCodeEnum stateCode) {
		this.stateCode = stateCode;
	}
	public Map<String, String> getMap() {
		return map;
	}
	public void setMap(Map<String, String> map) {
		this.map = map;
	}

	@Override
	public String toString() {
		return "Response [agreeMent=" + agreeMent + ", stateCode=" + stateCode + ", map=" + map + "]";
	}
}

上边的Request、Response类相比很容易就可以看出来是干什么用的。对于请求和响应有不清楚的可以详细查看本人博客

source文件的东西大家可以随便存放一些图片、网页、文本等等。

工具类SocketToRequest.java。主要就是把socket中的请求信息,通过IO流、字符串的拼接封装到Ruquest的属性中

public class SocketToRequest {
	public static Request socketToRequest(Socket socket) throws IOException {
		Request request = new Request();
		BufferedReader br = 
				new BufferedReader(new InputStreamReader(socket.getInputStream()));
		//读取socket的第一行,获取请求行
		String line = null;
		line = br.readLine();
		String[] split = line.split(" ");
		if(line != null) {
			//获取请求方式
			request.setRequestMethod(split[0].trim());
			/*获取请求的资源
			 *因为要区分GET和POST请求所以这里用 ? 把地址分开。前一部分是请求文件、后边是请求体
			 */
			String[] split2 = split[1].split("\\?");
			request.setRequestSource(split2[0]);
			//数组长度>1时,把地址中的参数赋值给请求体
			if(split2.length > 1) {
				request.setRequestBody(split2[1]);
			}
		}
		
		//获取请求头
		while ((line = br.readLine()) != null && !"".equals(line)) {
			split = line.split(":");
			request.getMap().put(split[0].trim(), split[1].trim());
		}
		
		/*
		 * 	 判断请求头里面有没有Content-Length
		 *   有则代表有请求体,否则没有
		 */
		if(request.getMap().containsKey("Content-Length")) {
			request.setRequestBody(br.readLine());
		}
		socket.shutdownInput();
		return request;
	}
}

枚举类StateCodeEnum,用来存放响应状态码

在这里我只写了两种状态码200 和 404

public enum StateCodeEnum {

	OK(
			200,"OK"),
	NOT_FOUND(
			404,"NOT_FOUND");
	
	private int num;
	private String str;
	
	private StateCodeEnum(int num,String str) {
		this.num = num;
		this.str = str;
	}
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public String getStr() {
		return str;
	}
	public void setStr(String str) {
		this.str = str;
	}
}

工具类FileTypeUtil.java

用来存放响应时的文件类型和字符编码,这里用map集合来存储。key值代表请求文件的后缀名,value值代表响应内容的类型和字符编码

浏览器发出请求,根据请求的地址文件的后缀名,服务器端做出相应的响应

public class FileTypeUtil {
	public static Map<String, String> map = null;
	
	static {
		map = new HashMap<>();
		map.put("jpg", "image/jpg;charset=utf-8");
		map.put("jpep", "images/jpeg;charset=utf-8");
		map.put("text", "text/html;charset=utf-8");
		map.put("html", "text/html;charset=utf-8");
		map.put("jsp", "text/html;charset=utf-8");
		map.put("pdf", "application/pdf;charset=utf-8");
	}
}

之前写的都是做的一些准备,主要的逻辑代码就在下边这个类中

类ThreadPoolImpl.java。用来给浏览器端进行响应

public class ThreadPoolImpl {

	public static final ExecutorService POOL;
	static {
		POOL = Executors.newCachedThreadPool();
	}
	//向浏览器端写入东西的方法
	@SuppressWarnings("unused")
	private static void runn(File file,String type,Response response,Socket socket) {
		try {
			response.setMap(FileTypeUtil.map);
			BufferedInputStream bis = 
					new BufferedInputStream(new FileInputStream(file));
			BufferedOutputStream bos = 
					new BufferedOutputStream(socket.getOutputStream());
              //这里用StringBuffer来进行字符串拼接,减少内存的消耗
			StringBuffer sb = new StringBuffer();
			//响应行的拼接 --->   HTTP协议 + 响应状态码 
			StringBuffer str1 = sb.append(response.getAgreeMent())
					.append(" ")
					.append(response.getStateCode().getNum())
					.append(" ")
					.append(response.getStateCode().getStr())
					.append("\r\n");
			bos.write(str1.toString().getBytes());
			//响应头  ---->   Content-Type: + 响应文件类型 + 响应字符编码
             //清空sb
			sb.delete(0, sb.length());
			StringBuffer str2 = sb.append("Content-Type:")
					.append(response.getMap().get(type))
					.append("\r\n");
			bos.write(str2.toString().getBytes());
			bos.write("\r\n".getBytes());
			
			//响应体  ----->   向浏览器写入文件
			int len = 0;
			byte[] by = new byte[1024];
			while ((len = bis.read(by)) != -1) {
				bos.write(by, 0, len);
				bos.flush();
			}
			socket.shutdownOutput();
			bos.close();
			socket.close();
		} catch (Exception e) {
			System.out.println("响应异常: " + e.getMessage());
		}
	}
	
	//响应的方法
	@SuppressWarnings("unused")
	private static void server(Socket socket,Response response,Request request) {
        //请求的文件名
		String fileName = null;
        //响应的文件
		File file = null;
        //响应的文件类型
		String type = null;
		//如果状态是OK,则正常响应页面
		if(response.getStateCode().equals(StateCodeEnum.OK)) {
            //通过请求获取文件名
			fileName = request.getRequestSource();
            //通过请求的文件名,获取source中的文件
			file = new File("source", fileName);
            //获取文件的后缀名,也就是文件类型,用来获取响应的类型
			type = fileName.substring(fileName.lastIndexOf(".") + 1);
            //向浏览器端做出响应
			runn(file, type, response, socket);
		}
		//如果是NOT_FOUND则响应“资源不存在”
		else if (response.getStateCode().equals(StateCodeEnum.NOT_FOUND)) {
            //文件不存在,则在source中找到404.html文件
			file = new File("source", "404.html");	
            //做出响应,响应资源未找到
			runn(file, "html", response, socket);
		}
	}
	
	//get请求时的响应代码
	private static void doGet(Socket socket,Response response,Request request) {
        //获取响应体
		String body = request.getRequestBody();
        //以下代码针对登录页面请求时做出的响应
		if(body != null && !"".equals(body)) {
            //通过&对响应体分割,前一部分为username=? 后一部分为 password=?
			String[] split = body.split("&");
            //如果username=1并且password=1则正常跳转页面
			if("username=1".equals(split[0]) && "password=1".equals(split[1])) {
				runn(new File("source", "login.html"), "html", response, socket);
			}else {
                //反正跳转登录失败界面
				runn(new File("source", "def.html"), "html", response, socket);
			}
		}else {
            //如果不是登录页面的请求,则直接执行以下代码
			server(socket, response, request);
		}
	}
	
	//post请求时的响应代码
	private static void doPost(Socket socket,Response response,Request request) {
		doGet(socket, response, request);
	}
	//根据请求方式做出响应
	public static void serverCode(Socket socket,Response response,Request request) {
		POOL.execute(()->{
			if ("GET".equals(request.getRequestMethod())) {
				ThreadPoolImpl.doGet(socket, response, request);
			}else if ("POST".equals(request.getRequestMethod())) {
				ThreadPoolImpl.doPost(socket, response, request);
			}
		});
	}
}

main方法

	public static void main(String[] args) {
		try {
			ServerSocket ss = new ServerSocket(10086);
			while (true) {
				Socket socket = ss.accept();
				System.out.println("服务器连接成功!");
				//把socket转换成request对象
				Request request = SocketToRequest.socketToRequest(socket);
                 //获取请求的文件名
				String fileName = request.getRequestSource();
				
				//通过请求获取source中的文件
				File file = new File("source", fileName);
				Response response = new Response();
				//判断文件是否存在
				if(file.exists()) {
					//文件存在,响应状态码为OK
					response.setStateCode(StateCodeEnum.OK);
                      //给客户端发送数据
					ThreadPoolImpl.serverCode(socket, response, request);
				}else {
                      //文件不存在,响应状态码为  NOT_FOUND
					response.setStateCode(StateCodeEnum.NOT_FOUND);
                      //响应一个资源不存在的页面
					ThreadPoolImpl.serverCode(socket, response, request);
				}
			}
		} catch (Exception e) {
			System.out.println("服务器响应异常:" + e.getMessage());
		}
	}

在这里插入图片描述

哦~,一定要点个赞。创作不容易!

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值