一个基于Java的HTTP服务器demo
前面几篇博客,大致介绍了几个方面的例子,例如报文解析、报文组装等。现在打算将这些东西集合起来,编写一个小HTTP服务器demo。期间遇到了很多问题,也解决了很多问题,有些不好或者无法解决的问题,也就妥协了一点,采用硬编码的方式来解决了。哈哈!
代码写的不是很好,毕竟我是正在学习的。这个程序可以保证,在正确的操作下,访问还是正常的。因为HTTP服务器通常是很复杂的,我这里是学习初期,一切都只是能让程序运行起来就行了。(可用性)
HTTP学习(4)–违反协议的错误
基本功能介绍
demo基本功能
我主要期待它具有以下功能:查看所有图片、上传图片、查看单张图片。
上面几个功能正好涉及到了:报文解析、报文组装、接收请求、响应请求的功能。
web的路由为:
图片页:/ 和 /index.mmp
上传页:/upload.mmp
上传处理:/upload
web图标:/favicon.ico
查看图片页:/pictures
未知路由:无
demo结构
本demo采用基本的Java工程,使用任何Java IDE都可以,无外部依赖jar包。
效果演示
演示GIF
demo图片路径
当demo启动后,会读取这个路径中的文件,并使用一个静态 Map 记录其文件名。当用户查找某一个图片时,系统会将图片响应给用户。当用户上传图片后,静态 Map 同样也会记录图片的名称,返回 index 页面时,也会同样显示新上传的图片。
代码部分
这个demo可以看作是一个HTTP文件服务器了,虽然它很简单,也不够完善。编码过程中有很多的硬编码,或者是不通用的方式。但是它作为我们了解、学习 HTTP 是足够了。
DragonServerBoot类
说明: 启动服务的主类。
package com.dragon.server;
public class DragonServerBoot {
public static void main(String[] args) {
int port = 9999;
DragonServer server = new DragonServer(port);
server.start();
}
}
DragonServer类
说明: 提供服务的类。这里通过一个静态的线程安全的 Map 集合来存储数据。 因为我需要的数据量很少,最多几十张图片就足够了,我也没想真用它来搭建服务。所以,只用一个 Map 就可以了,不需要使用数据库这种重量级的软件了。
注意: 这里处理四种类型的图片:.jpg .jpeg .gif .png。并且这里类型是大小写敏感的,这不是关键问题,我也就简单处理了。
package com.dragon.server;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DragonServer {
private int port;
private static ServerSocket server;
public static Map<String, String> picMap = Collections.synchronizedMap(new HashMap<>());
static {
Path path = Paths.get("D:", "DragonFile", "ServerImg");
File picDir = path.toFile();
File[] files = picDir.listFiles((dir, f)->f.contains(".jpg") || f.contains(".jpeg") || f.contains(".gif") || f.contains(".png"));
for (File file : files) {
String filename = file.getName();
picMap.put(filename, filename);
}
}
public DragonServer(int port) {
this.port = port;
}
public void start() {
try {
server = new ServerSocket(port);
System.out.println("服务启动成功...");
this.receiveRequest();
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务启动失败!");
}
}
public void receiveRequest() {
ExecutorService pool = Executors.newFixedThreadPool(10);
while (true) {
try {
Socket client = server.accept();
System.out.println("用户"+client.getInetAddress().toString()+"建立连接" + client.toString());
pool.submit(new Connection(client));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void stop() {
try {
if (server != null) {
server.close();
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器关闭失败!");
}
}
}
Request类
说明: Request类用于解析用户请求报文,Response类用于组装响应报文,并发送给用户。
前面的博客里面已经介绍过了这两个类的大致功能。
前面的博客里面已经介绍了 Request 类的大致功能,这里只是把它拿出来作为一个单独的类进行使用。并且多了一个方法 private void splitHeader(String header)
来解析具体的信息,这里主要来说一下它的功能:
首先请求报文的第一行是请求行,它的结构和下面的头部是不同的,所以使用一个标志位 flag 来单独处理。
请求行的结构:
GET /pictures/d18d2a1d-051e-4692-83f4-8d6b8deb239c.png HTTP/1.1
POST /upload HTTP/1.1
第一个参数是请求方法,第二个参数是请求路径和请求参数(GET方式),第三个是协议的版本。
注意:GET方式请求路径和请求参数是在 一起的,POST方式请求路径在第一行,请求参数是在报文体部分。
说明: 这里的处理很是麻烦,因为我也是第一次这样做。我这个demo的功能很简单,所以只有查看图片的GET方式是带有请求参数的,其它的GET方式是没有请求参数,所以他们的请求路径和参数就是请求路径了。(/ 和 /index.mmp /upload.mmp 方式)
所以我就对查看图片的方式进行特殊处理,如果路径中含有 /pictures,那么这个路径就是查看图片的了,并且假定它的格式一定为 /pictures/jsdjfksl.jpg 这种形式,否则程序也是处理不了的。(因为我确实是没有接触过这些东西的处理方式,所以这里是一种妥协的处理方式,只要系统运行起来就行了,哈哈!)
if (flag) {
String[] requestLine = header.split("\\s");
this.method = requestLine[0];
if ("POST".equalsIgnoreCase(this.method)) {
this.path = requestLine[1];
} else {
if (requestLine[1].contains("/pictures")) {
int pos = requestLine[1].lastIndexOf("/");
this.path = requestLine[1].substring(0, pos);
paramsMap.put("filename", requestLine[1].substring(pos+1));
}
else {
this.path = requestLine[1];
}
}
package com.dragon.server;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class Request {
private InputStream in;
private boolean flag = true;
private String method;
private String path;
private Map<String, String> paramsMap = new HashMap<>();
public Request(InputStream in) {
this.in = new BufferedInputStream(in);
}
public void resolveRequest() {
try {
String header = null;
do {
header = this.getHeader();
if ("".equals(header)) break;
this.splitHeader(header);
System.out.println("header:-----> " + header);
} while (true);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private String getHeader() throws IOException {
StringBuilder header = new StringBuilder();
while (true) {
int c = in.read();
if (c == '\n' || c == -1) break;
header.append((char)c);
}
return header.toString().trim();
}
private void splitHeader(String header) {
System.out.println();
if (flag) {
String[] requestLine = header.split("\\s");
this.method = requestLine[0];
if ("POST".equalsIgnoreCase(this.method)) {
this.path = requestLine[1];
} else {
if (requestLine[1].contains("/pictures")) {
int pos = requestLine[1].lastIndexOf("/");
this.path = requestLine[1].substring(0, pos);
paramsMap.put("filename", requestLine[1].substring(pos+1));
}
else {
this.path = requestLine[1];
}
}
flag = false;
} else {
String[] headLine = header.split(":", 2);
paramsMap.put(headLine[0].trim(), headLine[1].trim());
}
}
public String getMethod() {
return this.method;
}
public String getPath() {
return this.path;
}
public String getParam(String param) {
return paramsMap.get(param);
}
public InputStream getInputStream() {
return this.in;
}
}
Response类
这个类主要是用于组装报文的,然后使用对象的 out 属性,发送响应报文 。前面的博客已经大致介绍了它的功能,这里只是把它作为一个单独的类拿来使用了。
package com.dragon.server;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class Response {
private static final String BLANK = " ";
private static final String CRLF = "\r\n";
private OutputStream out;
public Response(OutputStream out) {
this.out = out;
}
public OutputStream getOutputStream() {
return this.out;
}
public byte[] getHeader(String contentType, long length, int statusCode, String phrase) {
return new StringBuilder()
.append("HTTP/1.0").append(BLANK).append(statusCode).append(BLANK).append(phrase).append(CRLF)
.append("Server:"+"CrazyDragon").append(CRLF)
.append("Date:").append(BLANK).append(this.getDate()).append(CRLF)
.append("Content-Type:").append(BLANK).append(contentType).append(CRLF)
.append("Content-Length:").append(BLANK).append(length).append(CRLF).append(CRLF)
.toString()
.getBytes(Charset.forName("UTF-8"));
}
private String getDate() {
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
return format.format(date);
}
public void close() {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Connection类
处理路由
在用户请求到来时,会首先使用Request类来解析请求报文的头部,剩下数据部分等待进一步处理(如果请求包含数据部分的话)。
不同的用户请求对应着不同的处理方式,相信具有web经验的人都是了解的。所以这里需要一个路由方式来处理用户的请求。 我这里就考虑使用简单的switch来完成这个功能了。(毕竟我的功能其实很简单,太复杂了我也不知道是怎么做的了。)
package com.dragon.server;
import java.io.IOException;
import java.net.Socket;
public class Connection implements Runnable {
private Socket client;
public Connection(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
Request request = new Request(client.getInputStream());
request.resolveRequest();
Response response = new Response(client.getOutputStream());
System.out.println("请求路径: " + request.getPath());
switch (request.getPath()) {
case "/":
case "/index.mmp":
Index index = new Index(request, response);
index.doGet();
break;
case "/favicon.ico":
Favicon favicon = new Favicon(request, response);
favicon.doGet();
break;
case "/upload.mmp":
UploadPage uploadPage = new UploadPage(request, response);
uploadPage.doGet();
break;
case "/upload":
PicUpload picUpload = new PicUpload(request, response);
picUpload.doGet();
break;
case "/pictures":
Pictures pictures = new Pictures(request, response);
pictures.doGet();
break;
default:
NotFound notFound = new NotFound(request, response);
notFound.doGet();
break;
}
} catch(IOException e) {
e.printStackTrace();
}
}
}
说明
前面介绍了基本的几个类的作用,下一篇博客会介绍具体实现功能的类。如果都写着一起的话,太长了,估计很多人都看不下去,所以我再写一篇博客继续上面未完的内容。
HTTP学习(5)–demo编写(2)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)