tomcat是Apache、Sun 和其他一些公司及个人共同开发而成。经常用于与并发性不高的中小型系统中。为了了解web编程我们对类似于tomcat的服务器软件进行初探析并且自己动手写简易版tomcat。
前面我们编写了网络编程多个客户端的相互交互程序。现在我们将浅谈一下与浏览器之间的交互。当然我们从最基础的http协议开始。http协议是在tcp协议上再次进行包装,是应用层间的协议。即在tcp的基础上再次添加了相关的 头信息以便识别。基础头信息如下:
GET / HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
HTTP/1.1 200 OK
Server:bjsxt Server/0.0.1
Date:Thu Sep 06 08:59:11 GMT+08:00 2018
infoContent-type:text/html;charset=UTF-8
infoContent-Length:72
当然这只是get请求方式和应答的最基础头信息。
只要tcp传送数据的基础上添加应答信息头浏览器就可以识别,以下就是基础的应答编写:
public class HttpServer {
private String HH="\r\n";
private String KG=" ";
private ServerSocket socket;
public static void main(String[] args) {
HttpServer server=new HttpServer();
server.start();
}
private void start() {
try {
socket=new ServerSocket(8888);
this.recive();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void recive() {
try {
StringBuffer msg=new StringBuffer();
String string=null;
Socket socket1=socket.accept();
byte[] bytes=new byte[2048];
int len =socket1.getInputStream().read(bytes);
System.out.println(new String(bytes,0,len));
String msg1=msg.toString().trim();
System.out.println(msg1);
String string2="\r\n" +
"
StringBuffer respones=new StringBuffer();
respones.append("HTTP/1.1").append(KG).append("200").append(KG).append("OK").append(HH);
respones.append("Server:bjsxt Server/0.0.1").append(HH);
respones.append("Date:").append(new Date()).append(HH);
respones.append("Content-type:text/html;charset=UTF-8").append(HH);
respones.append("Content-Length:").append(string2.length()).append(HH);
respones.append(HH);
respones.append(string2);
System.out.println(respones.toString());
BufferedWriter output=new BufferedWriter(new OutputStreamWriter(socket1.getOutputStream(),"UTF-8"));
output.write(respones.toString());
output.flush();
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
当然在此基础上我们把请求和应答进行封装,这样在使用的时候就能够更加的清楚简单。同时呢当请求的时候具有相关的参数传递我们就需要在请求的数据上面进行拆分与分析,将传递的数据从请求报文中拆解出来并建立map
相关编写如下:
public class Request {
private Map
private BufferedInputStream reader;
private String requestInfo;
private String url;
private String content;
public Request(InputStream input) {
super();
map=new HashMap
try {
reader=new BufferedInputStream(input);
} catch (Exception e) {
e.printStackTrace();
}
}
public void request() {
byte[] data=new byte[20480];
try {
int len=reader.read(data);
requestInfo=new String(data, 0, len);
System.out.println(requestInfo);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void getContent() {
String method=requestInfo.substring(0,requestInfo.indexOf(" "));
if(method.equalsIgnoreCase("get"))
content=requestInfo.substring(requestInfo.indexOf("?")-1,requestInfo.indexOf("HTTP/1.1"));
else
if(method.equalsIgnoreCase("psot")||method.equalsIgnoreCase("POST"))
{
content=requestInfo.substring(requestInfo.lastIndexOf("\r\n")+2,requestInfo.length());
content=decode(content, "UTF-8");
}
get(content);
}
private void get(String content){
StringTokenizer stringTokenizer =new StringTokenizer(content, "&");
while(stringTokenizer.hasMoreTokens()) {
String string=stringTokenizer.nextToken();
String [] list=string.split("=");
if(list.length==1)
{
list=Arrays.copyOf(list,2);
list[1]=null;
}
for(int i=0;i
if(!map.containsKey(list[0]))
{
ArrayList
list2.add(list[1]);
map.put(list[0], list2);
continue;
}
map.get(list[0]).add(list[1]);
}
}
}
private String decode(String value,String code) {
try {
return java.net.URLDecoder.decode(value,code);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public List
request();
getContent();
return map.get(name);
}
}
response编写
public class Respones {
private StringBuffer header;
private static String BLANK=" ";
private static String NWELINE="\r\n";
private int len;
private StringBuffer content;
private BufferedWriter writer;
public Respones(OutputStream writer) {
header=new StringBuffer();
content=new StringBuffer();
len=0;
try {
this.writer=new BufferedWriter(new OutputStreamWriter(writer,"UTF-8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void header(int code) {
String state=null;
header.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
switch(code) {
case 200: state ="OK";break;
case 404: state ="NOT FOUND";break;
case 505: state ="SEVER ERROR";break;
}
header.append(state).append(NWELINE);
header.append("Server:bjsxt Server/0.0.1").append(NWELINE);
header.append("Date:").append(new Date()).append(NWELINE);
header.append("Content-type:text/html;charset=UTF-8").append(NWELINE);
header.append("Content-Length:").append(len).append(NWELINE);
header.append(NWELINE);
}
void push(int code) {
header(code);
try {
writer.append(header.toString());
writer.append(content.toString());
writer.flush();
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void printfln(String info) {
content.append(info).append(NWELINE);
len+=(info+NWELINE).length();
}
}
为了可以多次处理来访请求我们要是用多线程,而每一个请求没必要都创建一个实现Runnable接口类(现阶段其实是可以的因为我们只写了一个相关的serverlet,为了能够使用多个Serverlet往下再说多态的使用)所以我们将创建的多线程内容提出封装到Dispatcher类中。
public class Dispatcher implements Runnable{
private Request req;
private Respones rep;
private Socket socket;
public Dispatcher() {
super();
}
public void setSocket(Socket socket) {
this.socket = socket;
try {
rep=new Respones(socket.getOutputStream());
req =new Request(socket.getInputStream());
} catch (IOException e) {
}
}
public void run() {
Serverlet serverlet;
try {
serverlet = new Serverlet();
serverlet.service(req, rep);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rep.push(200);
}
}
编写Serverlet处理类:
public class Serverlet {
public void service(Request req,Respones rep) {
List
StringBuffer string2=new StringBuffer("\r\n" +
" \r\n" +
" \r\n");
for(String tem:list) {
string2.append(tem);
}
string2.append(
"" +
"");
rep.printfln(string2.toString());
}
}
编写服务接收端:
public class HttpServer2 {
private String HH="\r\n";
private String KG=" ";
private ServerSocket socket;
public static void main(String[] args) {
HttpServer2 server=new HttpServer2();
server.start();
}
private void start() {
try {
socket=new ServerSocket(8888);
this.recive();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void recive() {
Socket socket1;
while(true) {
try {
socket1 = socket.accept();
System.out.println(1);
Dispatcher serverlet02=new Dispatcher (socket1);
System.out.println(msg1);
Thread thread =new Thread(serverlet02);
thread.start();
System.out.println("=========================================");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
但是呢这样只能处理一个单一的serverlet无论请求端发什么带有username的参数的URL(localhost:8888/xxxx)都会只得到一个页面。因为处理serverlet只写了一个,而且在分发serverlet的Dispatcher类中我们写死了只生成创建的这一个serverlet类。所以需要在Dispatcher中生成的引用可以指向所有可以用的具体serverlet对象,这就要使用到父类。而具体的实现对象我们需要通过分析请求http头信息进行分析得到具体的对象。所以需要编写一个类用于得到具体的serverlet为了不使产生对象过多我们使用反射来进行创建对象。具体实现如下:
首先要有request和response的封装类:
public class Request {
private Map
private BufferedInputStream reader;
private String requestInfo;
private String url;
private String content;
public Request(InputStream input) {
super();
map=new HashMap
try {
reader=new BufferedInputStream(input);
} catch (Exception e) {
e.printStackTrace();
}
request();
getContent();
}
public void request() {
byte[] data=new byte[20480];
try {
int len=reader.read(data);
requestInfo=new String(data, 0, len);
System.out.println(requestInfo);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void getContent() {
String method=requestInfo.substring(0,requestInfo.indexOf(" "));
if(method.equalsIgnoreCase("get"))
{
if(!requestInfo.substring(0,requestInfo.indexOf("\r\n")).contains("?"))
{
content=null;
url=requestInfo.substring(requestInfo.indexOf(" ")+1,requestInfo.indexOf("HTTP/1.1")).trim();
}
else {
url=requestInfo.substring(requestInfo.indexOf(" ")+1,requestInfo.indexOf("?"));
content=requestInfo.substring(requestInfo.indexOf("?")+1,requestInfo.indexOf("HTTP/1.1")).trim();
content=decode(content, "UTF-8");
}
}
else
if(method.equalsIgnoreCase("post"))
{
url=requestInfo.substring(requestInfo.indexOf("/"),requestInfo.indexOf("HTTP/1.1")).trim();
content=requestInfo.substring(requestInfo.lastIndexOf("\r\n")+2,requestInfo.length());
content=decode(content, "UTF-8");
}
if(content!=null)
get(content);
}
private void get(String content){
StringTokenizer stringTokenizer =new StringTokenizer(content, "&");
while(stringTokenizer.hasMoreTokens()) {
String string=stringTokenizer.nextToken();
String [] list=string.split("=");
if(list.length==1)
{
list=Arrays.copyOf(list,2);
list[1]=null;
}
for(int i=0;i
if(!map.containsKey(list[0]))
{
ArrayList
list2.add(list[1]);
map.put(list[0], list2);
continue;
}
else
map.get(list[0]).add(list[1]);
}
}
}
private String decode(String value,String code) {
try {
return java.net.URLDecoder.decode(value,code);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public List
return map.get(name);
}
public String getUrl() {
return url;
}
}
public class Respones {
private StringBuffer header;
private static String BLANK=" ";
private static String NWELINE="\r\n";
private int len;
private StringBuffer content;
private BufferedWriter writer;
public Respones(OutputStream writer) {
header=new StringBuffer();
content=new StringBuffer();
len=0;
try {
this.writer=new BufferedWriter(new OutputStreamWriter(writer,"UTF-8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void header(int code) {
String state=null;
header.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
switch(code) {
case 200: state ="OK";break;
case 404: state ="NOT FOUND";break;
case 505: state ="SEVER ERROR";break;
}
header.append(state).append(NWELINE);
header.append("Server:bjsxt Server/0.0.1").append(NWELINE);
header.append("Date:").append(new Date()).append(NWELINE);
header.append("Content-type:text/html;charset=UTF-8").append(NWELINE);
header.append("Content-Length:").append(len).append(NWELINE);
header.append(NWELINE);
}
void push(int code) {
header(code);
try {
writer.append(header.toString());
writer.append(content.toString());
writer.flush();
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void printfln(String info) {
content.append(info).append(NWELINE);
len+=(info+NWELINE).length();
}
}
然后定义serverlet父类:
public abstract class Serverlet {
public void service(Request req,Respones rep) throws Exception{
this.doGet(req,rep);
this.doPost(req,rep);
}
public abstract void doGet(Request req,Respones rep);
public abstract void doPost(Request req,Respones rep);
}
具体的实现类
public class LoginServerlet extends Serverlet {
private int num=10;
@Override
public void doGet(Request req, Respones rep) {
num--;
List
List
StringBuffer string2=new StringBuffer("\r\n" +
" \r\n" + " \r\n");
for(String tem:list) {
string2.append(tem);
}
for(String tem:list1) {
string2.append(tem);
}
string2.append( num+
"" +
"");
rep.printfln(string2.toString());
}
@Override
public void doPost(Request req, Respones rep) {
}
}
public class RegisterServerlet extends Serverlet{
@Override
public void doGet(Request req, Respones rep) {
// TODO Auto-generated method stub
}
@Override
public void doPost(Request req, Respones rep) {
// TODO Auto-generated method stub
}}
之后写分发的Dispatcher类
public class Dispatcher implements Runnable{
private Request req;
private Respones rep;
private Socket socket;
public Dispatcher() {
super();
}
public void setSocket(Socket socket) {
this.socket = socket;
try {
rep=new Respones(socket.getOutputStream());
req =new Request(socket.getInputStream());
} catch (IOException e) {
}
}
public void run() {
Serverlet serverlet;
try {
serverlet = WebApp.getServer(req.getUrl());//通过url来找具体的类名然后通过WebApp的getServer创建处理该请求serverlet的类
serverlet.service(req, rep);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rep.push(200);
}
}
编写能够创建具体的serverlet类:
public class WebApp {
private static ServerContext serverContext;
private static Map
static {
serverContext=new ServerContext();//我们需要将所有serverlet的能够处理请求的url写入到一个map中然后多个请求url可以对应一个serverlet,并且可以对serverlet取别名(为以后的注解准备)在创建一个map放别名和对应的serverlet。然后进行封装。
Map
maping.put("/log", "login");
maping.put("/login", "login");
maping.put("/favicon.ico","login");
maping.put("/register", "register");
Map
serverlet.put("login", "httpserver02.LoginServerlet");
serverlet.put("register", "httpserver02.RegisterServerlet");
map=new HashMap
}
public static Serverlet getServer(String url) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Class> serverlet=Class.forName(serverContext.getServerlet().get(serverContext.getMaping().get(url)));
if(map.get(url)==null)
map.put(url, (Serverlet)serverlet.newInstance());
return map.get(url);
}
}
封装的两个map类:
public class ServerContext {
private Map
private Map
public ServerContext() {
maping=new HashMap
serverlet=new HashMap
}
public Map
return maping;
}
public void setMaping(Map
this.maping = maping;
}
public Map
return serverlet;
}
public void setServerlet(Map
this.serverlet = serverlet;
}
}
服务类:
public class Server {
private ServerSocket socket;
public static void main(String[] args) {
Server server=new Server();
server.start();
}
private void start() {
try {
socket=new ServerSocket(8888);
this.recive();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void recive() {
Socket socket1;
Dispatcher dispatcher=new Dispatcher();
while(true) {
try {
socket1=socket.accept();
if(socket1!=null)
dispatcher.setSocket(socket1);
new Thread(dispatcher).start();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
所以呢最终我们只要继承serverlet然后在service方法中写相关的输出字符串就行了。