1. 接收客户端的请求解析出它请求的文件名及相对路径.
2. 查找这个文件是否存在 不存在:404页面存在 :1.读取这个资源2. 构建响应协议
1.项目架构
2.kittyServer,创建一个ServerSocket,从配置文件中读取端口号,启动一个线程
package com.yc.http.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class KittyServer {
public static void main(String[] args) throws Exception {
KittyServer ks=new KittyServer();
ks.startServer();
}
boolean flag=false;
private void startServer() throws Exception{
ServerSocket ss=null;
int port=parseServerXml();
try {
ss=new ServerSocket(port);
YcConstants.logger.debug("kitty server is starting,and listening to port"+ss.getLocalPort());
} catch (IOException e) {
YcConstants.logger.debug("kitty server is port "+port+"is aleready in use...");
return;
}
while(!flag){
Socket s=ss.accept();
YcConstants.logger.debug("a client"+s.getInetAddress()+"is connecting to kittyserver");
TaskService ts=new TaskService(s);
Thread t=new Thread(ts);
t.start();
}
}
private int parseServerXml() throws Exception{
List<Integer> list=new ArrayList<Integer>();
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();//通过DocumentBuilderFactory创建xml解析器
try{
DocumentBuilder bulider=factory.newDocumentBuilder();//通过解释器创建一个可以加载并生成xml的DocumentBuilder
Document doc=bulider.parse(YcConstants.SERVERCONFIG);//通过DocumentBuilder加载并生成一颗xml树,Document对象的实例
NodeList nl=doc.getElementsByTagName("Connector");//通过document可以遍历这棵树,并读取相应的节点的内容
for(int i=0;i<nl.getLength();i++){
Element node=(Element)nl.item(i);
list.add(Integer.parseInt(node.getAttribute("port")));
}
}catch(Exception e){
throw e;
}
return list.get(0);
}
}
3.真正的任务,创建HttpServletRequest HttpServletResponse,并返回响应内容
package com.yc.http.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TaskService implements Runnable {
private Socket socket;
private InputStream iis;
private OutputStream oos;
private boolean flag;
public TaskService(Socket socket){
this.socket=socket;
try {
this.iis=this.socket.getInputStream();
this.oos=this.socket.getOutputStream();
flag=true;
} catch (IOException e) {
YcConstants.logger.error("failed to get stream ",e);
flag=false;
throw new RuntimeException(e);
}
}
@Override
public void run() {
if(flag){
//包装一个HttpServletRequest对象,从iis中读取到数据,解析请求信息,保存信息
HttpServletRequest request=new HttpServletRequest(this.iis);
//包装一个HttpServletResponse对象 ,从request中取文件资源,构建响应头,会给客户端
HttpServletResponse response=new HttpServletResponse(request, this.oos);
response.sendRedirect();
}
try {
this.socket.close();
} catch (IOException e) {
YcConstants.logger.error("failed to close connection to client ",e);
}
}
}
4.自定义HttpServletRequest
package com.yc.http.server;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.StringTokenizer;
public class HttpServletRequest {
private String method; //请求方法
private String protocal;//协议版本
private String serverName;//服务名
private int serverPort;//端口
private String requestURI;//资源的地址
private String requestURL;//绝对路径
private String contextPath;//项目上下文路径
private String realPath=System.getProperty("user.dir")+File.separatorChar+"webapps";//user.dir 取到时当前class的路径
private InputStream iis;
public HttpServletRequest(InputStream iis){
this.iis=iis;
parseRequest();
}
private void parseRequest(){
String requestInfoString=readFromInputstream();//从输入流读取请求头
if(requestInfoString==null || "".equals(requestInfoString)){
return;
}
//解析requestinfo字符串 method URI
parseRequestInfoString(requestInfoString);
}
private void parseRequestInfoString(String requestInfoString){
StringTokenizer st=new StringTokenizer(requestInfoString);//分割
// GET /res/aaa/index.html HTTP/1.1
if(st.hasMoreTokens()){
this.method=st.nextToken();
this.requestURI=st.nextToken();///res/aaa/index.html
this.protocal=st.nextToken();
this.contextPath="/"+ this.requestURI.split("/")[1];//contextPath上下文路径/res
}
//TODO: 后面的键值对 实体 暂时不管
}
//从输入流读取请数据
public String readFromInputstream(){
//1.从input 中读出所有的内容(http请求协议--》protocal)
String protocal=null;
//从 iis流中取protocal
StringBuffer sb=new StringBuffer(1024*10);//10k
int length=-1;
byte [] bs=new byte[1024 *10];
try {
length=this.iis.read(bs);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
length=-1;
}
for(int j=0;j<length;j++){
sb.append((char)bs[j]);
}
protocal=sb.toString();
return protocal;
}
public String getMethod() {
return method;
}
public String getProtocal() {
return protocal;
}
public String getServerName() {
return serverName;
}
public int getServerPort() {
return serverPort;
}
public String getRequestURI() {
return requestURI;
}
public String getRequestURL() {
return requestURL;
}
public String getContextPath() {
return contextPath;
}
public String getRealPath() {
return realPath;
}
}
5.自定义HttpServletResponse
package com.yc.http.server;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
public class HttpServletResponse {
private OutputStream oos;
private HttpServletRequest request;
public HttpServletResponse(HttpServletRequest request,OutputStream oos){
this.oos=oos;
this.request=request;
}
/*
* 从request中取出uri 2.判断是否在本地有个这文件 没有就404
* 以输出流将文件写到客户端,要加入响应的协议
*
*
*/
public void sendRedirect(){
String responseprotocal=null;//响应协议头
byte[] fileContent=null;//响应的内容
String uri=request.getRequestURI();//请求的资源的地址 /res/aaa/index.html
File f=new File(request.getRealPath(), uri); //请求的文件的绝对路径
if(!f.exists()){
fileContent=readFile(new File(request.getRealPath(),"/404.html"));
responseprotocal=gen404(fileContent.length);
}else{
fileContent=readFile(f);
responseprotocal=gen200(fileContent.length);
}
try {
oos.write(responseprotocal.getBytes());
oos.flush();
oos.write(fileContent);
oos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(oos!=null){
try {
oos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
private byte[] readFile(File f){
FileInputStream fis=null;
//字节数组输出流 :读取到字节数组后,存到内存
ByteArrayOutputStream boas=new ByteArrayOutputStream();
try{
//读取这个文件
fis=new FileInputStream(f);
byte [] bs=new byte[1024];
int length=-1;
while((length=fis.read(bs, 0, bs.length))!=-1){
boas.write(bs, 0, length);
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return boas.toByteArray();//一次性地从内存中读取所有的字节数组返回
}
private String gen200(long bodylength){
String uri=this.request.getRequestURI();//取出要访问的文件的地址
int index=uri.lastIndexOf(".");
if(index>=0){
index=index+1;
}
String fileExtension=uri.substring(index);//文件的后缀名
String protocal200="";
if("JPG".equalsIgnoreCase(fileExtension) || "JPEG".equalsIgnoreCase(fileExtension)){
protocal200="HTTP/1.1 200 OK\r\nContent-Type: image/JPEG\r\nContent-Length: "+bodylength+"\r\n\r\n";
}else if("PNG".equalsIgnoreCase(fileExtension)){
protocal200="HTTP/1.1 200 OK\r\nContent-Type: image/PNG\r\nContent-Length: "+bodylength+"\r\n\r\n";
}else if("json".equalsIgnoreCase(fileExtension)){
protocal200="HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: "+bodylength+"\r\n\r\n";
}else if("css".equalsIgnoreCase(fileExtension)){
protocal200="HTTP/1.1 200 OK\r\nContent-Type: text/css\r\nContent-Length: "+bodylength+"\r\n\r\n";
}else if("js".equalsIgnoreCase(fileExtension)){
protocal200="HTTP/1.1 200 OK\r\nContent-Type: text/javascript\r\nContent-Length: "+bodylength+"\r\n\r\n";
}else{
protocal200="HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: "+bodylength+"\r\n\r\n";
}
return protocal200;
}
//404响应
private String gen404(long bodylength){
String protocal404="HTTP/1.1 404 File Not Found\r\nContent-Type: text/html\r\nContent-Length: "+bodylength+"\r\n\r\n";
return protocal404;
}
}
5.定义了一些常量
package com.yc.http.server;
import org.apache.log4j.Logger;
public class YcConstants {
//配置server.xml文件名字
public final static String SERVERCONFIG="conf/server.xml";
/**
* 日志对象
*/
public final static Logger logger=Logger.getLogger(YcConstants.class);
}
用到的技术: ServerSocket -> Socket ,线程,dom解析,http协议
注意的问题:
1. HttpServletRequest类中的 private String readFromInputStream()方法,要一次读取所有的请求头数据.