http服务器实现

前言
在实践的过程中,我发现,协议理解的深浅,阅读协议文档 < 看协议实现源码 < 自己实现协议的代码。
深入学习http服务器,这是本文的目的,而不是实现一个真正可用的http服务器。毕竟实现一个成熟可用http服务器的难度很大。软件都经历过很多版本的迭代,在不断测试、bug调试和完善功能的过程中,*终才变得成熟可用的。像BAT等大公司听说也是用现有的成熟框架来裁剪开发服务器的。本文参考的源码有boa服务器源码。boa源码下载
本文只是一个服务器的框架程序,在接下来的文章中,我将一步一步完善这个http服务器的功能,并把实验的成果分享出来。我想体现的是一个程序从零开发的思路,因为当面对一大坨一大坨完整的程序,有时会显得很茫然,没经验的很难体会到作者的设计意图。
多年的经验告诉我,如果想要一次性写出完美程序,那么*后就可能因为无从下手而什么都没有写。允许缺陷,开始动手吧!无论过程多么丑陋,*后也会结出经验的果实。
这是*篇,希望自己能够坚持下去(确实写文章也需要花费很多时间)。

一、select机制
因为下文的程序框架运用到了select机制,这里有必要再回顾一下,参考我以前的博文:TCP socket select用法分析
首先,我们来看看select函数的定义和参数的含义:

int select( int nfds, fd_set FAR* readfds, fd_set * writefds, fd_set * exceptfds, const struct timeval * timeout)
1
参数含义:

nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的*大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
readfds:(可选)指针,指向一组等待可读性检查的套接口。
writefds:(可选)指针,指向一组等待可写性检查的套接口。
exceptfds:(可选)指针,指向一组等待错误检查的套接口。
timeout:select()*多等待时间,对阻塞操作则为NULL。
返回值:
select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;如果超时则返回0;否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError获取相应错误代码。

当返回为-1时,所有描述符集清0。
当返回为0时,表示超时。
当返回为正数时,表示已经准备好的描述符数。
select()返回后,在3个描述符集里,依旧是1的位就是准备好的描述符。这也就是为什么,每次用select后都要用FD_ISSET的原因。
select函数实现I/O多路复用,可以用来监视多个描述符,之后我们调用FD_ISSET函数确定具体是哪一个描述符准备好了。
那怎样才算准备好了呢?《unix环境高级编程》中,提到:

若对读集中的一个描述符进行的read操作不会阻塞,则认为此描述符是准备好的。
若对写集中的一个描述符进行的write操作不会阻塞,则认为此描述符是准备好的。
若对异常条件集中的一个描述符有一个未决异常条件,则认为此描述符是准备好的。
对于读、写和异常条件,普通文件的文件描述符总是认为准备好的。
操作select函数,还需要以下几个函数配合。
void FD_CLR(int fd, fd_set *set) // 清除set集合中描述符fd
int FD_ISSET(int fd, fd_set *set) //判断set集合中描述符fd是否准备好
void FD_SET(int fd, fd_set *set) //将描述符fd添加进集合set(其实是将某一位置1)。
void FD_ZERO(fd_set *set) //将set集全部清除

我们来看看下文使用到的select程序片段:

fd_set block_read_fdset;
int max_fd;
void select_loop(int server_s)
{
FD_ZERO(&block_read_fdset);
max_fd = server_s+1;

while (1) {
BOA_FD_SET(server_s, &block_read_fdset);
//没有可读的文件描述符,就阻塞。
if (select(max_fd + 1, &block_read_fdset,NULL, NULL,NULL) == -1) {
if (errno == EINTR)
continue;
else if (errno != EBADF) {
perror(“select”);
}
}
if (FD_ISSET(server_s, &block_read_fdset))
process_requests(server_s);

}
}

上面的程序,定义一个block_read_fdset读集合,然后调用FD_ZERO(&block_read_fdset) 初始化,接着把监听socket连接的文件描述符加入到读集合block_read_fdset。注意,这时的socket_s还不能用read或write进行读写,必须调用accept函数返回的描述符才行!在while循环中调用select函数监听是否有客户端连接进来,当有客户端向服务器发起connect的时候,则认为server_s描述符是准备好,select函数就会返回,否则select会一直阻塞。因为我们这里没有设置select的超时时间,所以当监听的描述符没有准备好的时候,select默认会阻塞。当select返回的时候,会把在block_read_fdset读集合中没有准备好的文件描述相对应的位给清零。select返回后,我们调用FD_ISSET判断block_read_fdset中server_s描述符对应的位是否被置1了,如果是,说明文件描述符可读,然后调用process_requests函数处理客户端的请求。因为这里select只有添加了一个server_s文件描述符,所以有没有用FD_ISSET判断都无所谓。但是为了适应以后多个描述符的情况,还是添加了FD_ISSET判断,方便移植。

二、服务器源码
下面的服务器程序中,在main函数使用了socket套接字创建TCP面向连接的套接字,流程已经模式化了,在*后调用select_loop函数处理客户端请求。select_loop函数在前面已经分析过了,*后会调用process_requests函数处理客户端的请求。我们可以看到process_requests函数调用accept函数,之后就可以利用accept函数返回的文件描述符来与客户端通信。使用read函数来读取客户端发送过来的信息,并打印到终端。程序显示了一个服务器基本的框架。在后续的文章中,我将在process_requests函数中解析http报文。协议类的都是这样子,客户端发送过来一连串的字符,服务器根据协议约定好的规则去解析这些报文,并根据解析出来的字段去干某些事。如果,客户端发送的是加密的字符,还需要解密之后再进行解析。

//web-server.c, an http server

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>

#define BUFFER_SIZE 4096
#define MAX_QUE_CONN_NM 5
#define PORT 6000
//#define MAXSOCKFD 10
#define FILE_NAME_MAX 512
#define SOCKADDR sockaddr_in
#define S_FAMILY sin_family
#define SERVER_AF AF_INET

fd_set block_read_fdset;
int max_fd;
#define BOA_FD_SET(fd, where) { FD_SET(fd, where); \
if (fd > max_fd) max_fd = fd; \
}

void select_loop(int server_s);
int process_requests(int server_s);

int main(int argc,char* argv[])
{
int sockfd;
int sin_size = sizeof(struct sockaddr);
struct sockaddr_in server_sockaddr, client_sockaddr;
int i = 1;/* 使得重复使用本地地址与套接字进行绑定 */

/*建立socket连接*/
if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
{
perror(“socket”);
exit(1);
}
printf(“Socket id = %d\n”,sockfd);

/*设置sockaddr_in 结构体中相关参数*/
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_sockaddr.sin_zero), 8);

setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

/*绑定函数bind*/
if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1)
{
perror(“bind”);
exit(1);
}
printf(“Bind success!\n”);

/*调用listen函数*/
if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
{
perror(“listen”);
exit(1);
}
printf(“Listening….\n”);
select_loop(sockfd);
return 0;
}

void select_loop(int server_s)
{
FD_ZERO(&block_read_fdset);
max_fd = server_s+1;
while (1) {
BOA_FD_SET(server_s, &block_read_fdset);
//没有可读的文件描述符,就阻塞。
if (select(max_fd + 1, &block_read_fdset,NULL, NULL,NULL) == -1) {
if (errno == EINTR)
continue; /* while(1) */
else if (errno != EBADF) {
perror(“select”);
}
}
if (FD_ISSET(server_s, &block_read_fdset))
process_requests(server_s);
}
}

int process_requests(int server_s)
{
int fd; /* socket */
struct SOCKADDR remote_addr; /* address */
int remote_addrlen = sizeof (struct SOCKADDR);
size_t len;
char buff[BUFFER_SIZE];
bzero(buff,BUFFER_SIZE);
//remote_addr.S_FAMILY = 0xdead;
fd = accept(server_s, (struct sockaddr *) &remote_addr,
&remote_addrlen);

if (fd == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
/* abnormal error */
perror(“accept”);
return -1;
}

int bytes = read(fd, buff, BUFFER_SIZE);
if (bytes < 0) {
if (errno == EINTR)
bytes = 0;
else
return -1;
}
printf(“recv from client:%s\n”,buff);
return 0;
}

三、客户端测试程序
接下来,写一个简单的客户端程序来测试服务器程序。程序如下:

/*client.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>

#define PORT 6000
#define BUFFER_SIZE 4096
#define FILE_NAME_MAX 512

int main(int argc,char* argv[])
{
int sockfd;
//char buff[BUFFER_SIZE];
struct hostent *host;
struct sockaddr_in serv_addr;

if(argc != 2)
{
fprintf(stderr,”Usage: ./client Hostname(or ip address) \ne.g. ./client 127.0.0.1 \n”);
exit(1);
}

//地址解析函数
if ((host = gethostbyname(argv[1])) == NULL)
{
perror(“gethostbyname”);
exit(1);
}
//创建socket
if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror(“socket”);
exit(1);
}
bzero(&serv_addr,sizeof(serv_addr));
//设置sockaddr_in 结构体中相关参数
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT); //将16位的主机字符顺序转换成网络字符顺序
serv_addr.sin_addr = *((struct in_addr *)host->h_addr); //获取IP地址
bzero(&(serv_addr.sin_zero), 8); //填充0以保持struct sockaddr同样大小

//调用connect函数主动发起对服务器端的连接
if(connect(sockfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr))== -1)
{
perror(“connect”);
exit(1);
}

char buff[BUFFER_SIZE]= “<letter>getPwd</letter>”; //读写缓冲区
int count;
count=send(sockfd,buff,100,0);
if(count<0)
{
perror(“Send file informantion”);
exit(1);
}
printf(“client send OK count = %d\n”,count);
return 0;
}

程序很简单,只是使用socket的框架建立一个客户端,然后用send函数发送一段字符串。我们打开终端实验一下。
打开一个终端,A 终端,先运行服务器程序,结果如下:

ubuntu@ubuntu:~/project/web-server$ ./web-server
Socket id = 3
Bind success!
Listening….

然后再打开一个终端,B终端,运行客户端程序,结果如下:

ubuntu@ubuntu:~/project/web-server$ ./client 127.0.0.1
client send OK count = 100

再看看A终端,发现服务器程序接收到了来自客户端发送的字符串。

ubuntu@ubuntu:~/project/web-server$ ./web-server
Socket id = 3
Bind success!
Listening….
recv from client:<letter>getPwd</letter>

Web服务器工作原理详解

概述:Web服务器概念较为广泛,我们*常说的Web服务器指的是网站服务器,它是建立在Internet之上并且驻留在某种计算机上的程序。Web服务器可以向Web客户端(如浏览器)提供文档或其他服务,只要是遵循HTTP协议而设计的网络应用程序都可以是Web客户端。

Web服务器和HTTP服务器可以说是同一个东西,当然非得细分的话,HTTP服务器是建立在HTTP协议之上的提供文档浏览的服务器,更多的是提供静态的文件。而Web服务器涵盖了HTTP服务器(这一点可以自行百度百科), Web服务器不仅能够存储信息,还能在用户通过Web浏览器提供的信息的基础上运行脚本和程序。
Web服务器 约等于 HTTP服务器 + 其他服务

目前所熟知的Web服务器有很多,其*主流的是 Apache, Nginx, IIS
各大Web服务器的实现细节都不同,是为了某种情形而设计开发的。但是它们的基础工作原理是相同的,这也是本次基础篇所讲解的内容。

一、Web服务器工作原理图解
%title插图%num

首先我们暂时不考虑HTTP协议的各种请求方式,我们先跟着**(Web服务器工作原理总体描述01)这张图,将一次Web服务的工作流程过一遍,我们假设以浏览器作为客户端
(1) 用户做出了一个操作,可以是填写网址敲回车,可以是点击链接,可以是点击按键等,接着浏览器获取了该事件。
(2) 浏览器与对端服务程序建立TCP连接。
(3) 浏览器将用户的事件按照HTTP协议格式**打包成一个数据包,其实质就是在待发送缓冲区中的一段有着HTTP协议格式的字节流。
(4) 浏览器确认对端可写,并将该数据包推入Internet,该包经过网络*终递交到对端服务程序。
(5) 服务端程序拿到该数据包后,同样以HTTP协议格式解包,然后解析客户端的意图。
(6) 得知客户端意图后,进行分类处理,或是提供某种文件、或是处理数据。
(7) 将结果装入缓冲区,或是HTML文件、或是一张图片等。
(8) 按照HTTP协议格式将(7)中的数据打包
(9) 服务器确认对端可写,并将该数据包推入Internet,该包经过网络*终递交到客户端。
(10) 浏览器拿到包后,以HTTP协议格式解包,然后解析数据,假设是HTML文件。
(11) 浏览器将HTML文件展示在页面
以上为Web服务器工作基本原理。其实不难发现,这仅仅只是一个简单的网络通信。我们应该深信,作为一个服务器,其根本的工作无非有三个

接收数据 2. 发送数据 3. 数据处理
而Web服务器的本质就是 接收数据 ⇒ HTTP解析 ⇒ 逻辑处理 ⇒ HTTP封包 ⇒ 发送数据
高级的服务器无非就是将这三个部分更加细致的设计了。
二、Web服务器之提供静态文件工作原理图解
Web服务器*主要的功能是提供静态的文件。日常的上网浏览大多是网页浏览,少数时候才会有一些数据的提交操作。因此,我们结合上一张图示来重点讲解在GET请求下的Web服务器工作原理。

%title插图%num

其他流程基本不变,着重在于红色与蓝色部分。
(1) 当用户点击一个网页链接或浏览器加载一些资源(css,jpg …)时产生。
(6) 服务程序解包后,确定其为GET请求,并且是对该服务器上的某一资源的请求。首先服务程序会去确认该路径是否存在,再确定该路径的文件是否可以获取。
(7-1) 如果请求的路径有误,或者该资源不能被用户获取,则返回错误提示页面。很多服务器的错误页面只有404,更专业的应该是将错误分类并返回对应的错误代码页面。
(7-2) 如果该路径合法且文件可以被获取,那么服务程序将根据该文件类型进行不同的装载过程,记录其类型作为(8)中HTTP协议中对应的返回类型,并加入响应头。

假设以点击一个页面链接为例,浏览器首先将HTML文件请求过来,再以同样的流程对HTML文件中包含的资源文件路径进行依次请求。

%title插图%num

三、Web服务器之数据提交工作原理图解
仅仅只是网页的浏览并不能满足所有人的需求,客户端与服务器应当是有数据交互的。
即使单方面的资源请求任然是网络的主力军。
我们应该清楚的知道,数据提交对于用户来说有什么作用。
(1) 资源上传 (2) 登陆验证 (3) API接口调用 (4) 远程指令等
数据提交使得用户的操作性有了质的飞跃,它使得HTTP短连接获取静态文件的方式提升到了动态交互的层次上。该性质也催化出各式各样的编程语言、框架。例如PHP,JavaWeb。
如果你留意目前主流的那些大型服务器,你会发现再高级再牛逼的东西实际是也是*基础的东西建造的。那么我们还可以顺便学习一下*古老的动态技术CGI
%title插图%num

其他流程基本不变,着重在于红色与蓝色部分。
(1) 用户提交数据,假设用户点击一个按键提交填好的信息。在(3)中将以POST格式写入,并填入提交至服务端的可执行程序的路径。
(6) 服务端将参数与该CGI绑定,复制进程,用管道传递参数和接收结果
(7) 子进程执行CGI,接收(6)父进程传来的参数,运算完成返回结果。
*后父进程将结果装入静态模板文件,放入缓冲区

四、动态技术
我们得明白,Web服务器是以短连接为主,并且获取的数据到达浏览器的那一刻一定是静态的不变的。那么所谓动态实际是指两种情况

服务端产生:
(1) 用户POST提交数据到某个程序,程序根据该数据作为参数运行,得出结果并装入静态的模板页面中,返回该静态页面。但对于用户来说,同一个页面,做了一个操作后数据不一样了。好了,这就是动态页面。(CGI原理)
(2) PHP的原理是,用户GET请求一个php后缀的文件,服务器先执行该php后缀文件中的PHP代码,将结果填入代码的位置,再返回。当然也可以提交数据参与运算再返回。
客户端产生:
(1) 用户GET请求一个JavaScript文件,服务端不做任何运算返回该静态文件。浏览器收到该JS文件,在本地执行并更新页面。
(2) 用户POST提交数据到服务端,服务端根据该提交的数据指令返回静态文件,浏览器收到后执行并更新。

 

用Java编写你自己的简单HTTP服务器

HTTP是个大协议,完整功能的HTTP服务器必须响应资源请求,将URL转换为本地系统的资源名。响应各种形式的HTTP请求(GET、POST等)。处理不存在的文件请求,返回各种形式的状态码,解析MIME类型等。但许多特定功能的HTTP服务器并不需要所有这些功能。例如,很多网站只是想显示“建设中“的消息。很显然,Apache对于这样的网站是大材小用了。这样的网站完全可以使用只做一件事情的定制服务器。Java网络类库使得编写这样的单任务服务器轻而易举。

定制服务器不只是用于小网站。大流量的网站如Yahoo,也使用定制服务器,因为与一般用途的服务器相比,只做一件事情的服务器通常要快得多。针对某项任务来优化特殊用途的服务器很容易;其结果往往比需要响应很多种请求的一般用途服务器高效得多。例如,对于重复用于多页面或大流量页面中的图标和图片,用一个单独的服务器处理会更好(并且还可以避免在请求时携带不必要的Cookie,因而可以减少请求/响应数据,从而减少下载带宽,提升速度);这个服务器在启动时把所有图片文件读入内存,从RAM中直接提供这些文件,而不是每次请求都从磁盘上读取。此外,如果你不想在包含这些图片的页面请求之外单独记录这些图片,这个单独服务器则会避免在日志记录上浪费时间。

本篇为大家简要演示三种HTTP服务器:

(1) 简单的单文件服务器

(2) 重定向服务器

(3) 完整功能的HTTP服务器

简单的单文件服务器

该服务器的功能:无论接受到何种请求,都始终发送同一个文件。这个服务器命名为SingleFileHTTPServer,文件名、本地端口和内容编码方式从命令行读取。如果缺省端口,则假定端口号为80。如果缺省编码方式,则假定为ASCII。
import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class SingleFileHTTPServer extends Thread { private byte[] content; private byte[] header; private int port=80; private SingleFileHTTPServer(String data, String encoding, String MIMEType, int port) throws UnsupportedEncodingException { this(data.getBytes(encoding), encoding, MIMEType, port); } public SingleFileHTTPServer(byte[] data, String encoding, String MIMEType, int port)throws UnsupportedEncodingException { this.content=data; this.port=port; String header=”HTTP/1.0 200 OK\r\n”+ “Server: OneFile 1.0\r\n”+ “Content-length: “+this.content.length+”\r\n”+ “Content-type: “+MIMEType+”\r\n\r\n”; this.header=header.getBytes(“ASCII”); } public void run() { try { ServerSocket server=new ServerSocket(this.port); System.out.println(“Accepting connections on port “+server.getLocalPort()); System.out.println(“Data to be sent:”); System.out.write(this.content); while (true) { Socket connection=null; try { connection=server.accept(); OutputStream out=new BufferedOutputStream(connection.getOutputStream()); InputStream in=new BufferedInputStream(connection.getInputStream()); StringBuffer request=new StringBuffer(); while (true) { int c=in.read(); if (c==’\r’||c==’\n’||c==-1) { break; } request.append((char)c); } //如果检测到是HTTP/1.0及以后的协议,按照规范,需要发送一个MIME首部 if (request.toString().indexOf(“HTTP/”)!=-1) { out.write(this.header); } out.write(this.content); out.flush(); } catch (IOException e) { // TODO: handle exception }finally{ if (connection!=null) { connection.close(); } } } } catch (IOException e) { System.err.println(“Could not start server. Port Occupied”); } } public static void main(String[] args) { try { String contentType=”text/plain”; if (args[0].endsWith(“.html”)||args[0].endsWith(“.htm”)) { contentType=”text/html”; } InputStream in=new FileInputStream(args[0]); ByteArrayOutputStream out=new ByteArrayOutputStream(); int b; while ((b=in.read())!=-1) { out.write(b); } byte[] data=out.toByteArray(); //设置监听端口 int port; try { port=Integer.parseInt(args[1]); if (port<1||port>65535) { port=80; } } catch (Exception e) { port=80; } String encoding=”ASCII”; if (args.length>2) { encoding=args[2]; } Thread t=new SingleFileHTTPServer(data, encoding, contentType, port); t.start(); } catch (ArrayIndexOutOfBoundsException e) { System.out.println(“Usage:java SingleFileHTTPServer filename port encoding”); }catch (Exception e) { System.err.println(e);// TODO: handle exception } } }
SingleFileHTTPServer类本身是Thread的子类。它的run()方法处理入站连接。此服务器可能只是提供小文件,而且只支持低吞吐量的web网站。由于服务器对每个连接所需完成的所有工作就是检查客户端是否支持HTTP/1.0,并为连接生成一两个较小的字节数组,因此这可能已经足够了。另一方面,如果你发现客户端被拒*,则可以使用多线程。许多事情取决于所提供文件的大小,每分钟所期望连接的峰值数和主机上Java的线程模型。对弈这个程序复杂写的服务器,使用多线程将会有明显的收益。

Run()方法在指定端口创建一个ServerSocket。然后它进入无限循环,不断地接受连接并处理连接。当接受一个socket时,就会由一个InputStream从客户端读取请求。它查看*行是否包含字符串HTTP。如果包含此字符串,服务器就假定客户端理解HTTP/1.0或以后的版本,因此为该文件发送一个MIME首部;然后发送数据。如果客户端请求不包含字符串HTTP,服务器就忽略首部,直接发送数据。*后服务器关闭连接,尝试接受下一个连接。

而main()方法只是从命令行读取参数。从*个命令行参数读取要提供的文件名。如果没有指定文件或者文件无法打开,就显示一条错误信息,程序退出。如果文件能够读取,其内容就读入byte数组data.关于文件的内容类型,将进行合理的猜测,结果存储在contentType变量中。接下来,从第二个命令行参数读取端口号。如果没有指定端口或第二个参数不是0到65535之间的整数,就使用端口80。从第三个命令行参数读取编码方式(前提是提供了)。否则,编码方式就假定为ASCII。然后使用这些值构造一个SingleFileHTTPServer对象,开始运行。这是唯一可能的接口。

下面是测试的结果:
命令行编译代码并设置参数:

%title插图%num

telnet::

首先,启用telnet服务(如果不会,自行google之),接着测试该主机的端口:

%title插图%num

结果(可以看到请求的输出内容):

%title插图%num

HTTP协议测试:

%title插图%num

文档(这是之前一篇文章–小车动画的文档):

%title插图%num

重定向服务器

实现的功能——将用户从一个Web网站重定向到另一个站点。下例从命令行读取URL和端口号,打开此端口号的服务器可能速度会很快,因此不需要多线程。尽管日次,使用多线程可能还是会带来一些好处,尤其是对于网络带宽很低、吞吐量很小的网站。在此主要是为了演示,所以,已经将该服务器做成多线程的了。这里为了简单起见,为每个连接都启用了一个线程,而不是采用线程池。或许更便于理解,但这真的有些浪费系统资源并且显得低效。

import java.io.BufferedInputStream; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.net.BindException; import java.net.ServerSocket; import java.net.Socket; import java.util.Date; public class Redirector implements Runnable { private int port; private String newSite; public Redirector(String site, int port){ this.port=port; this.newSite=site; } @Override public void run() { try { ServerSocket server=new ServerSocket(port); System.out.println(“Redirecting connection on port” +server.getLocalPort()+” to “+newSite); while (true) { try { Socket socket=server.accept(); Thread thread=new RedirectThread(socket); thread.start(); } catch (IOException e) { // TODO: handle exception } } } catch (BindException e) { System.err.println(“Could not start server. Port Occupied”); }catch (IOException e) { System.err.println(e); } } class RedirectThread extends Thread { private Socket connection; RedirectThread(Socket s) { this.connection=s; } public void run() { try { Writer out=new BufferedWriter( new OutputStreamWriter(connection.getOutputStream(),”ASCII”)); Reader in=new InputStreamReader( new BufferedInputStream(connection.getInputStream())); StringBuffer request=new StringBuffer(80); while (true) { int c=in.read(); if (c==’\t’||c==’\n’||c==-1) { break; } request.append((char)c); } String get=request.toString(); int firstSpace=get.indexOf(‘ ‘); int secondSpace=get.indexOf(‘ ‘, firstSpace+1); String theFile=get.substring(firstSpace+1, secondSpace); if (get.indexOf(“HTTP”)!=-1) { out.write(“HTTP/1.0 302 FOUND\r\n”); Date now=new Date(); out.write(“Date: “+now+”\r\n”); out.write(“Server: Redirector 1.0\r\n”); out.write(“Location: “+newSite+theFile+”\r\n”); out.write(“Content-Type: text/html\r\n\r\n”); out.flush(); } //并非所有的浏览器都支持重定向, //所以我们需要生成一个适用于所有浏览器的HTML文件,来描述这一行为 out.write(“<HTML><HEAD><TITLE>Document moved</TITLE></HEAD>\r\n”); out.write(“<BODY><H1>Document moved</H1></BODY>\r\n”); out.write(“The document “+theFile +” has moved to \r\n<A HREF=\””+newSite+theFile+”\”>” +newSite+theFile +”</A>.\r\n Please update your bookmarks”); out.write(“</BODY></HTML>\r\n”); out.flush(); } catch (IOException e) { }finally{ try { if (connection!=null) { connection.close(); } } catch (IOException e2) { } } } } /** * @param args */ public static void main(String[] args) { int thePort; String theSite; try { theSite=args[0]; //如果结尾有’/’,则去除 if (theSite.endsWith(“/”)) { theSite=theSite.substring(0,theSite.length()-1); } } catch (Exception e) { System.out.println(“Usage: java Redirector http://www.newsite.com/ port”); return; } try { thePort=Integer.parseInt(args[1]); } catch (Exception e) { thePort=80; } Thread t=new Thread(new Redirector(theSite, thePort)); t.start(); } }
HTTP测试:
侦听8010端口,此处重定向到百度:

%title插图%num

%title插图%num

main()方法提供一个非常简单的界面,读取新网站的URL(为了把链接重定向到该URL)和监听本地端口。它使用这些信息构造了一个Rredirector对象。然后它使用所生成的Runnable对象(Redirector实现了Runnable)来生成一个新线程并启动。如果没有指定端口,Rredirector则会监听80端口。

Redirectro的run()方法将服务器socket绑定与此端口,显示一个简短的状态消息,然后进入无限循环,监听连接。每次接受连接,返回的Socket对象会用来构造一个RedirectThread。然后这个RedirectThread被启动。所有与客户端进一步的交互由此新线程完成。Redirector的run()方法只是等待下一个入站连接。

RedirectThread的run()方法完成了很多工作。它先把一个Writer链接到Socket的输出流,把一个Reader链接到Socket的输入流。输入流和输出流都有缓冲。然后run()方法读取客户端发送的*行。虽然客户端可能会发送整个Mime首部,但我们会忽略这些。*行包含所有所需的信息。这一行内容可能会是这样:

GET /directory/filename.html HTTP/1.0

可能*个词是POST或PUT,也可能没有HTTP版本。

返回的输出,*行显示为:

HTTP/1.0 302 FOUND

这是一个HTTP/1.0响应吗,告知客户端要被重定向。第二行是“Date:”首部,给出服务器的当前时间。这一行是可选的。第三行是服务器的名和版本;这一行也是可选的,但蜘蛛程序可用它来统计记录*流行的web服务器。下一行是“Location:”首部,对于此服务器这是必须的。它告知客户端要重定向的位置。*后是标准的“Content-type:”首部。这里发送内容类型text/html,只是客户端将会看到的HTML。*后,发送一个空行来标识首部数据的结束。

如果浏览器不支持重定向,那么那段HTML标签就会被发送。

功能完整的HTTP服务器

这里,我们来开发一个具有完整功能的HTTP服务器,成为JHTTP,它可以提供一个完整的文档树,包括图片、applet、HTML文件、文本文件等等。它与SingleFileHTTPServer非常相似,只不过它所关注的是GET请求。此服务器仍然是相当轻量级的;看过这个代码后,我们将讨论可能希望添加的其他特性。

由于这个服务器必须为可能很慢的网络连接提供文件系统的大文件,因此要改变其方式。这里不再在执行主线程中处理到达的每个请求,而是将入站连接放入池中。由一个RequestProcessor类实例从池中移走连接并进行处理。

import java.io.File; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import org.omg.CORBA.Request; public class JHTTP extends Thread { private File documentRootDirectory; private String indexFileName=”index.html”; private ServerSocket server; private int numThreads=50; public JHTTP(File documentRootDirectory,int port , String indexFileName)throws IOException { if (!documentRootDirectory.isDirectory()) { throw new IOException(documentRootDirectory+” does not exist as a directory “); } this.documentRootDirectory=documentRootDirectory; this.indexFileName=indexFileName; this.server=new ServerSocket(port); } private JHTTP(File documentRootDirectory, int port)throws IOException { this(documentRootDirectory, port, “index.html”); } public void run(){ for (int i = 0; i < numThreads; i++) { Thread t=new Thread(new RequestProcessor(documentRootDirectory, indexFileName)); t.start(); } System.out.println(“Accepting connection on port ” +server.getLocalPort()); System.out.println(“Document Root: “+documentRootDirectory); while (true) { try { Socket request=server.accept(); RequestProcessor.processRequest(request); } catch (IOException e) { // TODO: handle exception } } } /** * @param args */ public static void main(String[] args) { File docroot; try { docroot=new File(args[0]); } catch (ArrayIndexOutOfBoundsException e) { System.out.println(“Usage: java JHTTP docroot port indexfile”); return; } int port; try { port=Integer.parseInt(args[1]); if (port<0||port>65535) { port=80; } } catch (Exception e) { port=80; } try { JHTTP webserver=new JHTTP(docroot, port); webserver.start(); } catch (IOException e) { System.out.println(“Server could not start because of an “+e.getClass()); System.out.println(e); } } }
JHTTP类的main()方法根据args[0]设置文档的根目录。端口从args[1]读取,或者使用默认的80.然后构造一个新的JHTTP线程并启动。此JHTTP线程生成50个RequestProcessor线程处理请求,每个线程在可用时从RequestProcessor池获取入站连接请求。JHTTP线程反复地接受入站连接,并将其放在RequestProcessor池中。每个连接由下例所示的RequestProcessor类的run()方法处理。此方法将一直等待,直到从池中得到一个Socket。一旦得到Socket,就获取输入和输出流,并链接到阅读器和书写器。接着的处理,除了多出文档目录、路径的处理,其他的同单文件服务器。

import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.net.Socket; import java.util.Date; import java.util.List; import java.util.LinkedList; import java.util.StringTokenizer; public class RequestProcessor implements Runnable { private static List pool=new LinkedList(); private File documentRootDirectory; private String indexFileName=”index.html”; public RequestProcessor(File documentRootDirectory,String indexFileName) { if (documentRootDirectory.isFile()) { throw new IllegalArgumentException(); } this.documentRootDirectory=documentRootDirectory; try { this.documentRootDirectory=documentRootDirectory.getCanonicalFile(); } catch (IOException e) { } if (indexFileName!=null) { this.indexFileName=indexFileName; } } public static void processRequest(Socket request) { synchronized (pool) { pool.add(pool.size(),request); pool.notifyAll(); } } @Override public void run() { //安全性检测 String root=documentRootDirectory.getPath(); while (true) { Socket connection; synchronized (pool) { while (pool.isEmpty()) { try { pool.wait(); } catch (InterruptedException e) { } } connection=(Socket)pool.remove(0); } try { String fileName; String contentType; OutputStream raw=new BufferedOutputStream(connection.getOutputStream()); Writer out=new OutputStreamWriter(raw); Reader in=new InputStreamReader(new BufferedInputStream(connection.getInputStream()), “ASCII”); StringBuffer request=new StringBuffer(80); while (true) { int c=in.read(); if (c==’\t’||c==’\n’||c==-1) { break; } request.append((char)c); } String get=request.toString(); //记录日志 System.out.println(get); StringTokenizer st=new StringTokenizer(get); String method=st.nextToken(); String version=””; if (method==”GET”) { fileName=st.nextToken(); if (fileName.endsWith(“/”)) { fileName+=indexFileName; } contentType=guessContentTypeFromName(fileName); if (st.hasMoreTokens()) { version=st.nextToken(); } File theFile=new File(documentRootDirectory,fileName.substring(1,fileName.length())); if (theFile.canRead()&&theFile.getCanonicalPath().startsWith(root)) { DataInputStream fis=new DataInputStream(new BufferedInputStream(new FileInputStream(theFile))); byte[] theData=new byte[(int)theFile.length()]; fis.readFully(theData); fis.close(); if (version.startsWith(“HTTP “)) { out.write(“HTTP/1.0 200 OK\r\n”); Date now=new Date(); out.write(“Date: “+now+”\r\n”); out.write(“Server: JHTTP 1.0\r\n”); out.write(“Content-length: “+theData.length+”\r\n”); out.write(“Content-Type: “+contentType+”\r\n\r\n”); out.flush(); } raw.write(theData); raw.flush(); }else { if (version.startsWith(“HTTP “)) { out.write(“HTTP/1.0 404 File Not Found\r\n”); Date now=new Date(); out.write(“Date: “+now+”\r\n”); out.write(“Server: JHTTP 1.0\r\n”); out.write(“Content-Type: text/html\r\n\r\n”); out.flush(); } out.write(“<HTML>\r\n”); out.write(“<HEAD><TITLE>File Not Found</TITLE></HRAD>\r\n”); out.write(“<BODY>\r\n”); out.write(“<H1>HTTP Error 404: File Not Found</H1>”); out.write(“</BODY></HTML>\r\n”); } }else {//方法不等于”GET” if (version.startsWith(“HTTP “)) { out.write(“HTTP/1.0 501 Not Implemented\r\n”); Date now=new Date(); out.write(“Date: “+now+”\r\n”); out.write(“Server: JHTTP 1.0\r\n”); out.write(“Content-Type: text/html\r\n\r\n”); out.flush(); } out.write(“<HTML>\r\n”); out.write(“<HEAD><TITLE>Not Implemented</TITLE></HRAD>\r\n”); out.write(“<BODY>\r\n”); out.write(“<H1>HTTP Error 501: Not Implemented</H1>”); out.write(“</BODY></HTML>\r\n”); } } catch (IOException e) { }finally{ try { connection.close(); } catch (IOException e2) { } } } } public static String guessContentTypeFromName(String name) { if (name.endsWith(“.html”)||name.endsWith(“.htm”)) { return “text/html”; }else if (name.endsWith(“.txt”)||name.endsWith(“.java”)) { return “text/plain”; }else if (name.endsWith(“.gif”)) { return “image/gif”; }else if (name.endsWith(“.class”)) { return “application/octet-stream”; }else if (name.endsWith(“.jpg”)||name.endsWith(“.jpeg”)) { return “image/jpeg”; }else { return “text/plain”; } } }
不足与改善:
这个服务器可以提供一定的功能,但仍然十分简单,还可以添加以下的一些特性:

(1) 服务器管理界面

(2) 支持CGI程序和Java Servlet API

(3) 支持其他请求方法

(4) 常见Web日志文件格式的日志文件

(5) 支持多文档根目录,这样各用户可以有自己的网站

*后,花点时间考虑一下可以采用什么方法来优化此服务器。如果真的希望使用JHTTP运行高流量的网站,还可以做一些事情来加速此服务器。*点也是*重要的一点就是使用即时编译器(JIT),如HotSpot。JIT可以将程序的性能提升大约一个数量级。第二件事就是实现智能缓存。记住接受的请求,将*频繁的请求文件的数据存储在Hashtable中,使之保存在内存中。使用低优先级的线程更新此缓存。

java 编写简易的http服务器

java 编写简易的http服务器
想要写一个http服务器,首先得了解http协议。这里仅介绍一下相关的知识。

1、http协议
HTTP请求报文
在请求中,HTTP报文由方法、URI、HTTP版本、HTTP首部字段等部分构成。其中方法、URI、HTTP版本作为首行,然后每个HTTP首部字段为一行,以上内容简称为请求头,如下请求头:

GET /favicon.ico HTTP/1.1 //首行,GET为请求方法,favicon.ico为url链接,1.1版本号
Host: 172.30.67.185:8080 //以下为首部字段行
Connection: keep-alive
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
注:请求报文一般由浏览器(客户端)向服务器发送

HTTP响应报文
在响应中,HTTP报文由HTTP版本、状态码、HTTP首部字段3部分构成,其中HTTP版本、状态码作为首行,然后每个HTTP首部字段为一行,以上内容简称为响应头,如下响应头:

HTTP/1.1 200 OK //首行,1.1为版本号,200为状态码。以下行为首行字段
Content-Type:text/html;charset=utf-8

//空一行后,响应客户端的内容,后面会讲
注:请求报文一般由服务器向浏览器(客户端)发送

2、简单的http服务器
服务器启动程序
首先,编写一个服务器启动程序,我们监听了8080端口,然后得到socket对象,接着new SocketServer(s),然后启动线程。线程池简单看作启动线程的工具就行了(需要深入了解线程池的自行百度)。

public static void main(String[] args) {
ServerSocket server=null;
//线程池,简单来说,就是把线程start()改成ex.execute(),然后可以提高服务器性能
ExecutorService ex=Executors.newFixedThreadPool(20);
try {
//建立服务器监听端口
server=new ServerSocket(8080);
while(true) {
Socket s=server.accept();
//然后把接收到socket传给SocketServer并执行该线程
ex.execute(new SocketServer(s));
}
} catch (IOException e) {
e.printStackTrace();
}
}
接着,因为我们的SocketServer需要做的是做一个http服务器,所以重点来了!怎么接受浏览器(客户端)传来的请求呢?然后又怎么给浏览器(客户端)响应内容呢?

http服务器类
因为浏览器(客户端)的请求方法有GET、POST等,在这里,先写一个响应GET的请求。

思路:

1、打开socket对象的输入流,接收浏览器(客户端)的请求头

2、分析字段内容,判断请求方法、链接

3、打开socket对象的输出流,在输出流里写入响应头

4、空一行,写入响应内容。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class SocketServer implements Runnable{
Socket s;
//构造方法
public SocketServer(Socket s) {
this.s=s;
}
//线程
public void run(){
//输入输出流
OutputStream os=null;
InputStream in=null;
try
{ //打开socket输入流,并转为BufferedReader流
in=s.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(in));

//接收*行,得到请求路径
String requestHeader;
requestHeader=br.readLine();
int begin = requestHeader.indexOf(“/”)+1;
int end = requestHeader.indexOf(“HTTP/”);
String url=”E:\\sublime文件\\Html文件\\第二天案例\\”;
url=url+requestHeader.substring(begin, end);

//打开socket对象的输出流,写入响应头
os = s.getOutputStream();
os.write(“HTTP/1.1 200 OK\r\n”.getBytes());
os.write(“Content-Type:text/html;charset=utf-8\r\n”.getBytes());
os.write(“\r\n”.getBytes());

//空一行,写入响应内容。
File f=new File(url);
if(f.exists()) //判断请求的文件是否存在
{
FileInputStream fin=new FileInputStream(f);
byte []b=new byte[1024];
int len;
while((len=fin.read(b))!=-1)
{
os.write(b,0,len);
}
}
os.flush();
//如果os流没有关闭的话,浏览器会以为内容还没传输完成,将一直显示不了内容
os.close();

}

}
实现过程非常简单,简单到没有判断浏览器(客户端)的请求方法,直接返回了一个请求路径的文件。

WEB服务器、应用程序服务器、HTTP服务器区别

WEB服务器、应用程序服务器、HTTP服务器有何区别?IIS、Apache、Tomcat、Weblogic、WebSphere都各属于哪种服务器,这些问题困惑了很久,今天终于梳理清楚了:

Web服务器的基本功能就是提供Web信息浏览服务。它只需支持HTTP协议、HTML文档格式及URL。与客户端的网络浏览器配合。因为Web服务器主要支持的协议就是HTTP,所以通常情况下HTTP服务器和WEB服务器是相等的(有没有支持除HTTP之外的协议的web服务器,作者没有考证过),说的是一回事。

应用程序服务器(简称应用服务器),我们先看一下微软对它的定义:”我们把应用程序服务器定义为“作为服务器执行共享业务应用程序的底层的系统软件”。 就像文件服务器为很多用户提供文件一样,应用程序服务器让多个用户可以同时使用应用程序(通常是客户创建的应用程序)”

通俗的讲,Web服务器传送(serves)页面使浏览器可以浏览,然而应用程序服务器提供的是客户端应用程序可以调用(call)的方法(methods)。确切一点,你可以说:Web服务器专门处理HTTP请求(request),但是应用程序服务器是通过很多协议来为应用程序提供(serves)商业逻辑 (business logic)。

以Java EE为例,Web服务器主要是处理静态页面处理和作为 Servlet容器,解释和执行servlet/JSP,而应用服务器是运行业务逻辑的,主要是EJB、 JNDI和JMX API等J2EE API方面的,还包含事务处理、数据库连接等功能,所以在企业级应用中,应用服务器提供的功能比WEB服务器强大的多。

以这样的定义,IIS、Apache、Tomcat都可以属于Web服务器,Weblogic、WebSphere都属于应用服务器。

Apache:在Web服务器中,Apache是纯粹的Web服务器,经常与Tomcat配对使用。它对HTML页面具有强大的解释能力,但是不能解释嵌入页面内的服务器端脚本代码(JSP/Servlet)。

Tomcat:早期的Tomcat是一个嵌入Apache内的JSP/Servlet解释引擎Apache+Tomcat就相当于IIS+ASP。后来的Tomcat已不再嵌入Apache内,Tomcat进程独立于Apache进程运行。 而且,Tomcat已经是一个独立的Servlet和JSP容器,业务逻辑层代码和界面交互层代码可以分离了。因此,有人把Tomcat叫做轻量级应用服务器。

IIS:微软早期的IIS,就是一个纯粹的Web服务器。后来,它嵌入了ASP引擎,可以解释VBScript和JScript服务器端代码了,这时,它就可以兼作应用服务器。当然,它与J2EE应用服务器根本无法相比,但是,从功能上说,从原理上说,它勉强可以称之为应用服务器。确切地说,它是兼有一点应用服务器功能的Web服务器。

综上:Apache是纯粹的web服务器,而Tomcat和IIS因为具有了解释执行服务器端代码的能力,可以称作为轻量级应用服务器或带有服务器功能的Web服务器。Weblogic、WebSphere因为能提供强大的J2EE功能,毫无疑问是*对的应用服务器。对于处于中间位置的Tomcat,它可以配合纯Web服务器Apache一起使用,也可以作为应用服务器的辅助与应用服务器一起部署:

一、Tomcat与应用服务器

到目前为止,Tomcat一直被认为是Servlet/JSP API的执行器,也就所谓的Servlet容器。然而,Tomcat并不仅仅如此,它还提供了JNDI和JMX API的实现机制。尽管如此,Tomcat仍然还不能算是应用服务器,因为它不提供大多数J2EE API的支持。

很有意思的是,目前许多的应用服务器通常把Tomcat作为它们Servlet和JSP API的容器。由于Tomcat允许开发者只需通过加入一行致谢,就可以把Tomcat嵌入到它们的应用中。遗憾的是,许多商业应用服务器并没有遵守此规则。

对于开发者来说,如果是为了寻找利用Servlet、JSP、JNDI和JMX技术来生成Java Web应用的话,选择Tomcat是一个优秀的解决方案;但是为了寻找支持其他的J2EE API,那么寻找一个应用服务器或者把Tomcat作为应用服务器的辅助,将是一个不错的解决方案;第三种方式是找到独立的J2EE API实现,然后把它们跟Tomcat结合起来使用。虽然整合会带来相关的问题,但是这种方式是*为有效的。。

二、Tomcat与Web服务器

Tomcat是提供一个支持Servlet和JSP运行的容器。Servlet和JSP能根据实时需要,产生动态网页内容。而对于Web服务器来说, Apache仅仅支持静态网页,对于支持动态网页就会显得无能为力;Tomcat则既能为动态网页服务,同时也能为静态网页提供支持。尽管它没有通常的Web服务器快、功能也不如Web服务器丰富,但是Tomcat逐渐为支持静态内容不断扩充。大多数的Web服务器都是用底层语言编写如C,利用了相应平台的特征,因此用纯Java编写的Tomcat执行速度不可能与它们相提并论。

一般来说,大的站点都是将Tomcat与Apache的结合,Apache负责接受所有来自客户端的HTTP请求,然后将Servlets和JSP的请求转发给Tomcat来处理。Tomcat完成处理后,将响应传回给Apache,*后Apache将响应返回给客户端。

而且为了提高性能,可以一台apache连接多台tomcat实现负载平衡。

关于WEB服务器、应用程序服务器的更详细区别可以参考下面这篇文章:

通俗的讲,Web服务器传送(serves)页面使浏览器可以浏览,然而应用程序服务器提供的是客户端应用程序可以调用(call)的方法(methods)。确切一点,你可以说:Web服务器专门处理HTTP请求(request),但是应用程序服务器是通过很多协议来为应用程序提供(serves)商业逻辑 (business logic)。

下面让我们来细细道来:

Web服务器(Web Server)

Web服务器可以解析(handles)HTTP协议。当Web服务器接收到一个HTTP请求(request),会返回一个HTTP响应 (response),例如送回一个HTML页面。为了处理一个请求(request),Web服务器可以响应(response)一个静态页面或图片,进行页面跳转(redirect),或者把动态响应(dynamic response)的产生委托(delegate)给一些其它的程序例如CGI脚本,JSP(JavaServer Pages)脚本,servlets,ASP(Active Server Pages)脚本,服务器端(server-side)JavaScript,或者一些其它的服务器端(server-side)技术。无论它们(译者注:脚本)的目的如何,这些服务器端(server-side)的程序通常产生一个HTML的响应(response)来让浏览器可以浏览。

要知道,Web服务器的代理模型(delegation model)非常简单。当一个请求(request)被送到Web服务器里来时,它只单纯的把请求(request)传递给可以很好的处理请求 (request)的程序(译者注:服务器端脚本)。Web服务器仅仅提供一个可以执行服务器端(server-side)程序和返回(程序所产生的)响应(response)的环境,而不会超出职能范围。服务器端(server-side)程序通常具有事务处理(transaction processing),数据库连接(database connectivity)和消息(messaging)等功能。

虽然Web服务器不支持事务处理或数据库连接池,但它可以配置(employ)各种策略(strategies)来实现容错性(fault tolerance)和可扩展性(scalability),例如负载平衡(load balancing),缓冲(caching)。集群特征(clustering—features)经常被误认为仅仅是应用程序服务器专有的特征。

应用程序服务器(The Application Server)

根据我们的定义,作为应用程序服务器,它通过各种协议,可以包括HTTP,把商业逻辑暴露给(expose)客户端应用程序。Web服务器主要是处理向浏览器发送HTML以供浏览,而应用程序服务器提供访问商业逻辑的途径以供客户端应用程序使用。应用程序使用此商业逻辑就象你调用对象的一个方法 (或过程语言中的一个函数)一样。

应用程序服务器的客户端(包含有图形用户界面(GUI)的)可能会运行在一台PC、一个Web服务器或者甚至是其它的应用程序服务器上。在应用程序服务器与其客户端之间来回穿梭(traveling)的信息不仅仅局限于简单的显示标记。相反,这种信息就是程序逻辑(program logic)。正是由于这种逻辑取得了(takes)数据和方法调用(calls)的形式而不是静态HTML,所以客户端才可以随心所欲的使用这种被暴露的商业逻辑。

在大多数情形下,应用程序服务器是通过组件 (component) 的应用程序接口(API)把商业逻辑暴露(expose)(给客户端应用程序)的,例如基于J2EE(Java 2 Platform, Enterprise Edition)应用程序服务器的EJB(Enterprise JavaBean)组件模型。此外,应用程序服务器可以管理自己的资源,例如看大门的工作(gate-keeping duties)包括安全(security),事务处理(transaction processing),资源池(resource pooling),和消息(messaging)。就象Web服务器一样,应用程序服务器配置了多种可扩展(scalability)和容错(fault tolerance)技术。

一个例子

例如,设想一个在线商店(网站)提供实时定价(real-time pricing)和有效性(availability)信息。这个站点(site)很可能会提供一个表单(form)让你来选择产品。当你提交查询 (query)后,网站会进行查找(lookup)并把结果内嵌在HTML页面中返回。网站可以有很多种方式来实现这种功能。我要介绍一个不使用应用程序服务器 的情景和一个使用应用程序服务器的情景。观察一下这两中情景的不同会有助于你了解应用程序服务器的功能。

情景1:不带应用程序服务器的Web服务器

在此种情景下,一个Web服务器独立提供在线商店的功能。Web服务器获得你的请求(request),然后发送给服务器端(server- side)可以处理请求(request)的程序。此程序从数据库或文本文件(flat file,译者注:flat file是指没有特殊格式的非二进制的文件,如properties和XML文件等)中查找定价信息。一旦找到,服务器端(server-side)程序把结果信息表示成(formulate)HTML形式,*后Web服务器把会它发送到你的Web浏览器。

简而言之,Web服务器只是简单的通过响应(response)HTML页面来处理HTTP请求(request)。

情景2:带应用程序服务器的Web服务器

情景2和情景1相同的是Web服务器还是把响应(response)的产生委托(delegates)给脚本(译者注:服务器端 (server-side)程序)。然而,你可以把查找定价的商业逻辑(business logic)放到应用程序服务器上。由于这种变化,此脚本只是简单的调用应用程序服务器的查找服务(lookup service),而不是已经知道如何查找数据然后表示为(formulate)一个响应(response)。这时当该脚本程序产生HTML响应(response)时就可以使用该服务的返回结果了。

在此情景中,应用程序服务器提供(serves)了用于查询产品的定价信息的商业逻辑。(服务器的)这种功能(functionality)没有指出有关显示和客户端如何使用此信息的细节,相反客户端和应用程序服务器只是来回传送数据。当有客户端调用应用程序服务器的查找服务(lookup service)时,此服务只是简单的查找并返回结果给客户端。

通过从响应产生(response-generating)HTML的代码中分离出来,在应用程序之中该定价(查找)逻辑的可重用性更强了。其他的客户端,例如收款机,也可以调用同样的服务(service)来作为一个店员给客户结帐。相反,在情景1中的定价查找服务是不可重用的因为信息内嵌在 HTML页中了。

总而言之,在情景2的模型中,在Web服务器通过回应HTML页面来处理HTTP请求(request),而应用程序服务器则是通过处理定价和有效性(availability)请求(request)来提供应用程序逻辑的。

警告(Caveats)

现在,XML Web Services已经使应用程序服务器和Web服务器的界线混淆了。通过传送一个XML有效载荷(payload)给服务器,Web服务器现在可以处理数据和响应(response)的能力与以前的应用程序服务器同样多了。

另外,现在大多数应用程序服务器也包含了Web服务器,这就意味着可以把Web服务器当作是应用程序服务器的一个子集(subset)。虽然应用程序服务器包含了Web服务器的功能,但是开发者很少把应用程序服务器部署(deploy)成这种功能(capacity)(译者注:这种功能是指既有应用程序服务器的功能又有Web服务器的功能)。相反,如果需要,他们通常会把Web服务器独立配置,和应用程序服务器一前一后。这种功能的分离有助于提高性能(简单的Web请求(request)就不会影响应用程序服务器了),分开配置(专门的Web服务器,集群(clustering)等等),而且给*佳产品的选取留有余地。

什么是http服务器

本篇文章旨在从服务器后台开发的角度剖析一个简单的http服务器的运行原理.

我们知道浏览器是http(s)的客户端,目的是连接远程的http服务器,然后服务器返回浏览器数据.浏览器接收数据解析数据之后展现出来.我们看到的外在表现就是,浏览器访问一个url,然后就得到相应的web页面.

同样我们知道,浏览器与http服务器是通过http协议,传输层是tcp协议,因为他是有连接,可靠的协议.关于http协议简单的介绍一下:

一个标准的HTTP请求由以下几个部分组成

<request-line>
<headers>
<CRLF>
[<request-body><CRLF>]
在HTTP请求中,*行是请求行(request-line),用来说明请求类型、要访问的资源(URL)以及使用的HTTP版本;
紧接着是多行头部(headers)信息,用来说明服务器要使用的附加信息;
头部信息之后是一个回车换行符(\r\n),用于标明头部信息的结束。
以上是必须内容,根据需要可在头部信息结束之后增加主体数据(request-body);

主体数据之后是一个回车换行符(\r\n),用于标明主体数据的结束。

例如,我们可以在IE浏览器上输入下面的网址:

http://localhost:8000/hello/index.html
HTTP请求的头部信息如下:
GET /hello/index.html HTTP/1.1
Accept: */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
Host: localhost:8000
Connection: Keep-Alive
Cookie: JSESSIONID=BBBA54D519F7A320A54211F0107F5EA6
收到请求数据之后,服务器解析,毕竟是明文字符,这个简单.然后服务器就知道了客户端的要求–获取目录hello/index.html文件.服务器读取文件内容发送给浏览器就好了.

后来随着业务逻辑越来越复杂,单单获取某个html文件功能早已不能满足需求,个性化需求呼之欲出.比如在线问卷调查表,他究竟是怎么把我们填写的数据传递给服务器的呢?

你可能会说那不是一样,客户端发送什么内容,服务器就接收什么内容.可是你想过没有,每个网站的需求是不一样的,本来服务器接收到浏览器的请求数据已经是够复杂的了,还让服务器来解析数据并响应不同的数据处理,这不太现实.

一般的,服务器*好只接收数据,如果让服务器也处理数据逻辑,势必会让服务器变得很复杂,稳定性也得不到保证.

另外一个角度是为了让程序复用,提高生产效率.也就是说,如果不关注业务逻辑,只注重接收数据,那么服务器程序可以给任何一个开发者使用.换句话说,我们不用从头开始写.直接使用现有的高性能的服务器就可以满足需求了.例如公司白领中午要吃饭,不可能跑回家自己去做饭吃,自己叫外卖就好了.

但是现实问题仍然没有解决,通过什么方式去处理业务逻辑呢?

你要给手机充电时,把插头插入插线板就能获取电了.插线板有接口,提供了电.

同理服务器程序*好也提供接口,浏览器通过统一的接口给服务器,然后我们从服务器接口中获取我们想要的数据.获取数据之后我们可以把数据交给第三方程序来处理逻辑,这样就做到与服务器业务分离了,good iead.

事实上,现在的http服务器就是这么做的,不过很复杂而已.下一篇开始为您介绍这些接口.

附录:HTTP Request Header 请求头

%title插图%num

区分Web服务器、HTTP服务器、应用程序服务器

在学习前端的过程中。进程听到和看到web服务器、HTTP服务器、应用程序服务器,但一直不知道它们有什么区别,迷惑了好久,今天查看的很多博客,终于算是梳理通了,下面我就来总结一下它们的区别,顺别了解一些服务器。

首先我们要知道web服务器它只需支持HTTP协议、HTML文档格式及URL。其主要功能是传送页面使浏览器可以浏览,又因为它主要支持HTTP协议,所以通常情况下web服务器和HTTP服务器是相等的。通俗讲web服务器就是专门用来处理HTTP请求的。

应用程序服务器可以简称为应用服务器,它主要的功能就是为客户端应用程序提供可调用的方法(应用程序提供(serves)商业逻辑)。

以javaEE为例,Web服务我主要处理静态页面和作为Servlet容器,解释和执行servlet/jsp,而应用服务器是运行业务逻辑的。

以这样的定义可对一些常见的服务器进行分类,IIS、Apache、Tomcat都可以属于Web服务器,Weblogic、WebSphere都属于应用服务器。下面就来了解一下这些服务器的主要作用:

Apache

在Web服务器中,Apache是纯粹的Web服务器,经常与Tomcat配对使用。它对HTML页面具有强大的解释能力,但是不能解释嵌入页面内的服务器端脚本代码(JSP/Servlet)。

Tomcat

早期的Tomcat是一个嵌入Apache内的JSP/Servlet解释引擎,Apache+Tomcat就相当于IIS+ASP(动态服务器页面)。后来的Tomcat已不再嵌入Apache内,Tomcat进程独立于Apache进程运行。 而且,Tomcat已经是一个独立的Servlet和JSP容器,业务逻辑层代码和界面交互层代码可以分离了。因此,有人把Tomcat叫做轻量级应用服务器。

IIS

微软早期的IIS,就是一个纯粹的Web服务器。后来,它嵌入了ASP引擎,可以解释VBScript和JScript服务器端代码了,这时,它就可以兼作应用服务器。从原理上说,它勉强可以称之为应用服务器。确切地说,它是兼有一点应用服务器功能的Web服务器。

综上所诉:

Apache是纯粹的web服务器,而Tomcat和IIS因为具有了解释执行服务器端代码的能力,可以称作为轻量级应用服务器或带有服务器功能的Web服务器。Weblogic、WebSphere因为能提供强大的J2EE功能,毫无疑问是*对的应用服务器。对于处于中间位置的Tomcat,它可以配合纯Web服务器Apache一起使用,也可以作为应用服务器的辅助与应用服务器一起部署