模拟web服务器 (小项目) 搭建+部署
模拟web服务器:可以从浏览器中访问到自己编写的服务器中的资源,将其资源显示在浏览器中。
技术选型:
-
-
线程池 同任务并发执行
-
IO流 传递数据 客户端也会像服务端发送数据, 服务器像客户端发送数据也是流
-
网络(Socket编程) 通过网络获取客户端的数据以及通过网络给客户端返回数据
-
-
ubuntu
-
使用c/s架构,只编写服务器端,客户端由浏览器代替,浏览器向服务器发送一个请求Request,服务器给浏览器返回一个响应Response。
使用的是HTTP协议。
项目整体的文件结构:
应用包appliction,包含启动类
封装包pojo,包含了两个封装类 ,request类(请求类) response类(响应类)为了防止后序有对象信息的传输,所以提前给两个类实现Serializable接口(序列化、反序列化)。
工具包util,包含请求的文件类型FileTpye,线程池工具PoolUtil,Socket转换成Request对象,和Request转换成Response对象的工具类SocketTrans,装载着状态码信息的一个接口类 StateCodeEnum。
所需的文件资源放在了source文件下。
GET请求方式:
浏览器向服务器发请求:http://127.0.0.1:80/index.html
请求数据为:
//请求行包括:请求方式+空格+/+请求资源+空格+协议版本+会车换行符 GET /index.html HTTP/1.1 //请求方式为GET 请求资源为index.html 协议版本为HTTP/1.1 回车换行符为\r\n
//请求头:请求行下的都是请求头 包含客户端的一些基本信息 Host: 127.0.0.1:80 //客户端的ip和端口 都是以空格隔开 Connection: keep-alive //客户端的连接状态 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 //客户端的基本信息 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 //客户端可以接收的数据类型 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9
发送一个GET请求由几部分组成:
请求行(请求方式,/请求资源名,协议名字/版本号)会车符 换行符
请求头 key:value 回车符 换行符
POST请求方式:由Postman软甲配合完成 选择POST方式 输入ip 端口 访问的资源
http://127.0.0.1:9999/login
从Body中输入key value参数
输入结束后连接即可。因为POST方式是向指定的资源提交要被处理的数据 ,查询字符串POST方式是在POST请求的HTTP主体中发送,而GET方式查询字符串是在GET请求的URL中发送,所以使用Postman方式进行模拟。
Request:需要封装请求行中的请求方式,用来根据不同的请求方式做不同的处理(GET没有请求体,而POST有请求体)、封装请求行中的请求资源,用来根据不同的请求资源发送不同的响应体、封装请求行中的协议版本(协议版本一般固定)、封装请求头(请求头中的内容都是key value值,所以使用一个map进行封装)、封装请求体。
import java.io.Serializable; import java.util.HashMap; import java.util.Map; public class Request implements Serializable{ private static final long serialVersionUID = 1L; //请求方式 private String requestMethod; //请求资源 private String requestSource; //协议版本 private String agreement; //请求头 private Map<String, String> requestHead = new HashMap<String, String>(); //请求体 private String requestBody; public String getRequestSource() { return requestSource; } public void setRequestSource(String requestSource) { this.requestSource = requestSource; } public String getAgreement() { return agreement; } public void setAgreement(String agreement) { this.agreement = agreement; } public Map<String, String> getRequestHead() { return requestHead; } public void setRequestHead(Map<String, String> requestHead) { this.requestHead = requestHead; } public String getRequestBody() { return requestBody; } public void setRequestBody(String requestBody) { this.requestBody = requestBody; } public String getRequestMethod() { return requestMethod; } public void setRequestMethod(String requestMethod) { this.requestMethod = requestMethod; } @Override public String toString() { return "Request [requestMethod=" + requestMethod + ", requestSource=" + requestSource + ", agreement=" + agreement + ", requestHead=" + requestHead + ", requestBody=" + requestBody + "]"; } }
Response:和请求对应,响应有响应行、响应头、响应体。响应行中有版本信息,状态码信息。响应头中包含的是key value类型的数据,用一个map封装,响应体用Object类型修饰,因为不知道响应的是什么类型的数据。
import java.io.Serializable; import java.util.HashMap; import java.util.Map; import com.briup.server.util.StateCodeEnum; public class Response implements Serializable{ private static final long serialVersionUID = 1L; //响应行的版本信息 private String agreement = "HTTP/1.1"; //响应行的状态码信息 private StateCodeEnum stateCode; //key值代表响应头的key value代表响应头的value private Map<String, String> responseHeader = new HashMap<String, String>(); //响应体 private Object responseBody; public String getAgreement() { return agreement; } public void setAgreement(String agreement) { this.agreement = agreement; } public StateCodeEnum getStateCode() { return stateCode; } public void setStateCode(StateCodeEnum stateCode) { this.stateCode = stateCode; } public Map<String, String> getResponseHeader() { return responseHeader; } public void setResponseHeader(Map<String, String> responseHeader) { this.responseHeader = responseHeader; } public Object getResponseBody() { return responseBody; } public void setResponseBody(Object responseBody) { this.responseBody = responseBody; } @Override public String toString() { return "Response [agreement=" + agreement + ", stateCode=" + stateCode + ", responseHeader=" + responseHeader + ", responseBody=" + responseBody + "]"; } }
FileType:响应头里的各种信息,根据key值得到响应的value值进行对响应头信息的封装。
import java.util.HashMap; import java.util.Map; public class FileType { public static final Map<String, String> TYPE; static { TYPE=new HashMap<>(); TYPE.put("txt","text/html;charset=tf-8"); TYPE.put("html","text/html;charset=tf-8"); TYPE.put("jpg","image/jpeg;charset=tf-8"); TYPE.put("jpeg","images/jpeg;charset=tf-8"); TYPE.put("png","image/png;charset=tf-8"); TYPE.put("mp4","video/mpeg4;charset=tf-8"); TYPE.put("pdf","application/pdf;charset=tf-8"); } }
PoolUtil:
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.Socket; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.print.attribute.ResolutionSyntax; import com.briup.server.pojo.Request; import com.briup.server.pojo.Response; public class PoolUtil { public static final ExecutorService pool; static { pool = Executors.newCachedThreadPool(); } private static void writeToBrowser(Socket socket,Response response) throws IOException { BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); StringBuilder builder = new StringBuilder(); builder.append(response.getAgreement()).append(" ").append(response.getStateCode().getCode()).append(" ").append(response.getStateCode().getMsg()).append("\r\n"); bos.write(builder.toString().getBytes()); //删除builder里面的数据 从0开始到最后 builder.delete(0, builder.length()); Set<Entry<String,String>> set = response.getResponseHeader().entrySet(); for (Entry<String, String> entry : set) { builder.append(entry.getKey()).append(" ").append(entry.getValue()).append("\r\n"); } //响应头 bos.write(builder.toString().getBytes()); //空行 bos.write("\r\n".getBytes()); //响应体 //如果是文件就写回文件, if(response.getResponseBody() instanceof File) { //将资源从本地磁盘读取 BufferedInputStream bis = new BufferedInputStream(new FileInputStream((File)response.getResponseBody())); int count=-1; byte []bytes = new byte[1024]; while((count=bis.read(bytes))!=-1) { bos.write(bytes, 0, count); } bos.flush(); socket.shutdownOutput(); } } private static void doGet(Request request,Response response,Socket socket) throws IOException { //通过request对象获取requestBody String string = request.getRequestBody(); if (string!=null &&!"".equals(string)) { String[] infos = string.split("[&]"); if ("username=123".equals(infos[0]) && "password=123".equals(infos[1])) { response.setStateCode(StateCodeEnum.OK); response.setResponseBody(new File("source/success.html")); } else { response.setStateCode(StateCodeEnum.LOGIN_FAIL); response.setResponseBody(new File("source/fail.html")); } } //将数据写回给浏览器 writeToBrowser(socket,response); } private static void doPost(Request request,Response response,Socket socket) throws IOException{ doGet(request, response, socket); writeToBrowser(socket,response); } public static void service( Request request,Response response,Socket socket) { pool.execute(()->{ try { if(("GET").equals(request.getRequestMethod())) { PoolUtil.doGet(request, response, socket); } else if(("POST").equals(request.getRequestMethod())){ PoolUtil.doPost(request, response, socket); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }); } }
SocketTrans:
import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import com.briup.server.pojo.Request; import com.briup.server.pojo.Response; public class SocketTrans { public static Request socketToRequest(Socket socket) throws IOException { if(socket ==null) { throw new RuntimeException("参数为空"); } Request request = new Request(); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); //得到请求行 String line = reader.readLine(); String[] split = line.split(" "); request.setAgreement(split[2]); request.setRequestMethod(split[0]); String[] split2 = split[1].split("[?]"); request.setRequestSource(split2[0]); //数组长度大于1 代表后面有数据 if(split2.length>1) { request.setRequestBody(split2[1]); } //得到请求头 while((line = reader.readLine())!=null&&!"".equals(line)) { String[] herderInfo = line.split(":"); request.getRequestHead().put(herderInfo[0], herderInfo[1].trim()); } //判断请求头里有没有Content-Length,有则代表有请求体 if (request.getRequestHead().containsKey("Content-Length")) { request.setRequestBody(reader.readLine()); } //封装完后 input流使用完毕了 socket.shutdownInput(); return request; } public static Response getResponse(Request request) { if(request==null) { throw new RuntimeException("参数为空"); } Response response = new Response(); //设置协议版本 response.setAgreement(request.getAgreement()); //判断资源是否存在 String fileName = request.getRequestSource(); File file = new File("source",fileName); //设置响应体 //得到请求资源的后缀名 fileName= fileName.substring(fileName.lastIndexOf(".")+1); //文件存在后缀名正确 不存在后缀名可能正确 也可能不正确 if(file.exists()) { response.setStateCode(StateCodeEnum.OK); response.setResponseBody(file); response.getResponseHeader().put("Content-Type:", FileType.TYPE.get(fileName)); }else { response.setStateCode(StateCodeEnum.NOT_FOUND); response.setResponseBody(new File("source/404.html")); response.getResponseHeader().put("Content-Type:", FileType.TYPE.get("html")); } return response; } }
StateCodeEnum :
public enum StateCodeEnum { OK(200,"OK"),NOT_FOUND(404,"NOT_FOUND"),LOGIN_FAIL(401,"UNAUTHORIZED"); private int code; private String msg; private StateCodeEnum(int code,String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
项目的代码基本结束,可以实现从浏览器端向服务器端。
将项目打包,右击项目,选择Export ,选择jarFile,选择要打包的地点。
将项目部署到Ubuntu的一个镜像中,方便测试,
在Ubuntu中,点击文件,点击打开,选中server.ovf(镜像文件),将其导入。
为方便远程操作服务器,使用一个远程连接工具srct814-x64软件。
在Ubuntu中查看ip 使用ifconfig ,在srct814-x64中添加此ip并连接。
在srct-x64中安装jdk,配置环境变量,使其具备Java环境。
在srct-x64中点击file,打开connect SFTP session ,将打包好的server.jar文件和资源文件夹source拖进这个窗口。
在该控制台窗口中整理文件,将source和jar包放在一个文件夹下,此时项目就算部署好了,在该窗口运行jar包
Java -jar server.jar
打开浏览器输入服务器的ip和端口号和你要访问的资源就可以访问到了。
如我的Ubuntu中ip是192.168.235.129 端口号为9999
在浏览器中输入 192.168.235.129:9999/1.png
就可以访问到服务器中的1.png文件