基于Http协议的Web服务端程序
(使用MAVEN项目,并要导入包dom4j)
(准备主页面index.html,404页面notfound.html,标签图–页面标签左边那个图-favicon.ico 均放入文件夹webapps中)
步骤:
1.创建一个MAVEN项目,并导入dom4j(在pom.xml中下载dom4j 这是使用的是1.6.1版)
2.在主目录下创建www.core包 主目录-src/main/java
3.创建类WebServer用于启动服务器
package www.core;
import java.net.ServerSocket;
import java.net.Socket;
public class WebServer {
private ServerSocket server;
public WebServer() throws Exception{
try {
server=new ServerSocket(8080);//设置端口号
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
public static void main(String[] args) {
try {
WebServer server=new WebServer();
server.start();
ClientHandler ch=new ClientHandler(socket);
Thread t=new Thread(ch);
t.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("服务器启动失败");
}
}
public void start(){
try {
System.out.println("等待服务器连接...");
Socket socket=server.accept();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.创建类ClientHandler用于处理客户端请求
package www.core;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* 处理客户端请求
*/
public class ClientHandler implements Runnable {
private Socket socket;
//参数用于接受WebServer响应的socket
public ClientHandler(Socket socket){
this.socket=socket;
}
/**
* 实现Runnable接口重写run方法以便用线程控制服务器响应
*/
public void run() {
try {
//创建输入流,读取客户端发送的数据
InputStream in=socket.getInputStream();
//创建输出流,用于响应客户端
OutputStream out=socket.getOutputStream();
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
5.在主目录下创建www.http包
并创建类HttpRequest用于处理客户端输入流
package www.http;
import java.io.InputStream;
/**
* 用于处理客户端输入流
*
*/
public class HttpRequest {
/*
* HTTP协议中的请求信息格式:
* 请求行
* GET /index.html HTTP/1.1CRLF
* CR:回车 LF:换行
*/
/*
* 创建三个成员变量获取上述关键字
*/
private String method;
private String uri;
private String protocol;
/**
* 创建构造方法,处理客户端的输入流,给成员变量赋值
*/
public HttpRequest(InputStream in){
try {
String line=readLine(in);
/*
* GET /index.html HTTP/1.1CRLF
* 拆分空格符获取对应的值
*/
String[]data=line.split(“\s”);
this.method=data[0];
this.uri=data[1];
this.protocol=data[2];
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 从给定的输入流中读取一行字符串并将其返回
* @param in
* @return
*/
public String readLine(InputStream in){
/*
* GET /index.html HTTP/1.1CRLF
* CR:回车-13 LF:换行-10
*/
StringBuilder builder=new StringBuilder();
try {
int ch1=-1;
int ch2=-1;
while((ch2=in.read())!=-1){
if(ch1==13&&ch2==10){
break;
}
builder.append((char)ch2);
ch1=ch2;
}
} catch (Exception e) {
e.printStackTrace();
}
return builder.toString().trim();
}
public String getMethod() {
return method;
}
public String getUri() {
return uri;
}
public String getProtocol() {
return protocol;
}
}
6.返回 类ClientHandler生成HttpRequest
HttpRequest request=new HttpRequest(in);
类ClientHandler:
package www.core;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import www.http.HttpRequest;
/**
* 处理客户端请求
*/
public class ClientHandler implements Runnable {
private Socket socket;
//参数用于接受WebServer响应的socket
public ClientHandler(Socket socket){
this.socket=socket;
}
/**
* 实现Runnable接口重写run方法以便用线程控制服务器响应
*/
public void run() {
try {
//创建输入流,读取客户端发送的数据
InputStream in=socket.getInputStream();
//创建输出流,用于响应客户端
OutputStream out=socket.getOutputStream();
// 生成HttpRequest表示客户端请求信息
HttpRequest request=new HttpRequest(in);
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
7.在包www.http中创建类HttpResponse
用于HTTP响应
定义存储HTTP响应信息的成员变量并添加get set方法
package www.http;
import java.io.OutputStream;
/**
* 封装Http响应
* HTTP响应格式
* 1:状态行
* 2:响应头
* 3:响应正文
*
* 状态行由3部分组成:协议版本,数字形式的状态代码,状态描述
* 如:HTTP/1.1 200 OK CRLF
*
* 响应头 响应头注明很多返回的信息,按行输出
*
* HTTP协议要求实际响应客户端时的数据格式: HTTP/1.1 200 OK CRLF 状态行
* Content-Type:text/html CRLF 响应头信息 用来指明发送给接收者的媒体类型
* Content-Length:100CRLF 响应头信息 用来指明发送给接收者实体正文的长度(发送过去的数据的字节量)
* CRLF 单独发送
* CRLF 指明响应头全部发送完毕
* DATA 实际数据
*/
public class HttpResponse {
/*
* 响应格式中需要的信息
*/
private OutputStream out;
private int status;
private String contentType;
private int contentLength;
public HttpResponse(OutputStream out){
this.out=out;
}
public OutputStream getOut() {
return out;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public int getContentLength() {
return contentLength;
}
public void setContentLength(int contentLength) {
this.contentLength = contentLength;
}
public Map<Integer, String> getStatusMap() {
return statusMap;
}
}
因为有很多数据需要记录所以需要另一个类来封装
将这些数据作为常量封装到另一个类中,通过类名点来调用
8.在主目录下创建www.common包并创建
类HttpContext用于定义HTTP协议中相关信息
package www.common;
/**
* 定义HTTP协议中相关信息
*
*/
public class HttpContext {
//回车 换行
public static final int CR=13;
public static final int LF=10;
//状态码及状态描述
public static final int STATUS_CODE_OK=200;
public static final String STATUS_VALUE_OK="OK";
public static final int STATUS_CODE_NOT_FOUND=404;
public static final String STATUS_VALUE_NOT_FOUND="Not Found";
public static final int STATUS_CODE_ERROR=500;
public static final String STATUS_VALUE_ERROR="Internal Server Error";
}
9.回到类ClientHandler
定义response 用于响应客户端 并判断客户端请求的uri及找到请求位置(准备请求页面,在根目录下创建文件夹webapps,并准备index.html作为首页,及notfound.html作为未找到页面时的页面)
为了方便找到请求页面,(在根目录(WebServer3项目目录并创建xml文件)下创建目录config)将页面地址写到config的server.xml中(index.html及notfound.html随意准备一下即可)
server.xml
webapps
notfound.html
10.在包www.common中创建类ServerContext用于记录服务端相关信息,将其封装
以便后期会有修改
package www.common;
import java.io.File;
import java.io.FileInputStream;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* 记录服务端相关信息
*
*/
public class ServerContext {
public static String web_root;
public static String notFoundPage;
static{
init();
}
private static void init(){
try {
SAXReader reader=new SAXReader();
Document doc=reader.read(new FileInputStream(“config”+File.separator+”server.xml”));
Element root=doc.getRootElement();//根目录为server
Element serviceEle=root.element("service");
web_root=serviceEle.elementText("webroot");
notFoundPage=serviceEle.elementText("not-found-page");
} catch (Exception e) {
e.printStackTrace();
}
}
}
11.回来类ClientHandler
获取客户端请求的页面,若页面存在则为response的成员变量赋值(状态 文件类型 文件长度)
并将文件类型封装成方法便于使用
HttpResponse response=new HttpResponse(out);
if(request.getUri()!=null){
File file=new File(ServerContext.web_root+File.separator+request.getUri());
if(file.exists()){
response.setStatus(HttpContext.STATUS_CODE_OK);
response.setContentType(getContentTypeByFile(file));
response.setContentLength((int)file.length());
}
}
//获取文件类型(实际就是截取文件的后缀名)
private String getContentTypeByFile(File file){
String fileName=file.getName();
String[] data=fileName.split(“\.”);
return data[1];
}
12.因为常见的Content-Type:有多种,而第11步中只能获取后缀并不能
正确表示该类型,所以可以将所有后缀对应的类型以XML标签属性的方式
写到server.xml中,并在类ServerContext中用MAP的方式存储起来
(该标签与service同级,均在server标签中)
定义成员变量:
public static Map