最近在学习JAVA网络编程编写HTTP服务器,HTTP是个大协议,完整功能的HTTP服务器必须响应资源请求,将URL转换为本地系统的资源名。响应各种形式的HTTP请求(GET、POST等)。处理不存在的文件请求,返回各种形式的状态码,解析MIME类型等。但许多特定功能的HTTP服务器并不需要所有这些功能。因此可用JAVA编写一个代替
首先来看利用的云服务器,选择的是新浪SAE,因为他免费,方便。
创建应用
在语言上选择JAVA
创建完成后进入代码管理
将编写好的代码打包成WAR 上传
这里可以选择用Myeclipise打包
首先需要注意的是,MyEclipse只能对WebProject类型的工程进行WAR包制作,对于我们常用的JavaProject则无法进行WAR包制作。
打开MyEclipse,在【Package Explorer】中选中需要压缩的项目,点击工具栏中的“File->Export…”,在弹出的【Export】对话框上,点击选中树状图中的“J2EE->WAR file (MyEclipse)”,点击【Next >】继续
在【WAR Export】对话框上选择需要压缩的项目名称,点击【Browse…】,在弹出的【另存为】对话框上选择WAR包保存的路径和名称,确认后点击【Finish】,开始进行压缩。
这里也可以用SVN进行代码的上传 不做说明
上传成功后就可以点击链接通过HTTP访问了
最后我们来看下代码:
1.AcceptHanlder.java
package Test;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class AcceptHandler implements Handler {
public void handle(SelectionKey key) throws IOException {
ServerSocketChannel serverSocketChannel=(ServerSocketChannel)key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel== null)return;
System.out.println("接收到客户连接,来自:" +
socketChannel.socket().getInetAddress() +
":" + socketChannel.socket().getPort());
ChannelIO cio =new ChannelIO(socketChannel, false/*非阻塞模式*/);
RequestHandler rh = new RequestHandler(cio);
socketChannel.register(key.selector(), SelectionKey.OP_READ, rh);
}
}
2.ChannelIO.java
package Test;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class ChannelIO {
protected SocketChannel socketChannel;
protected ByteBuffer requestBuffer;
private static int requestBufferSize = 4096;
public ChannelIO(SocketChannel socketChannel, boolean blocking)
throws IOException {
this.socketChannel = socketChannel;
socketChannel.configureBlocking(blocking); //设置阻塞模式
requestBuffer = ByteBuffer.allocate(requestBufferSize);
}
public SocketChannel getSocketChannel() {
return socketChannel;
}
/*
* 如果原缓冲区的剩余容量不够,就创建一个新的缓冲区,容量为原来的两倍,
* 把原来缓冲区的数据拷贝到新缓冲区
*/
protected void resizeRequestBuffer(int remaining) {
if (requestBuffer.remaining() < remaining) {
// 把容量增大到原来的两倍
ByteBuffer bb = ByteBuffer.allocate(requestBuffer.capacity() * 2);
requestBuffer.flip();
bb.put(requestBuffer); //把原来缓冲区中的数据拷贝到新的缓冲区
requestBuffer = bb;
}
}
/*
* 接收数据,把它们存放到requestBuffer中,如果requsetBuffer的剩余容量不足5%,
* 就调用resizeRequestBuffer()方法扩充容量
*/
public int read() throws IOException {
resizeRequestBuffer(requestBufferSize/20);
return socketChannel.read(requestBuffer);
}
/*
* 返回requestBuffer,它存放了所有的请求数据
*/
public ByteBuffer getReadBuf() {
return requestBuffer;
}
/*
* 发送参数指定的ByteBuffer中的数据
*/
public int write(ByteBuffer src) throws IOException {
return socketChannel.write(src);
}
/*
* 把FileChannel中的数据写到SocketChannel中
*/
public long transferTo(FileChannel fc, long pos, long len) throws IOException {
return fc.transferTo(pos, len, socketChannel);
}
/*
* 关闭SocketChannel
*/
public void close() throws IOException {
socketChannel.close();
}
}
3.Content.java
package Test;
/**
* 表示服务器发送给客户的正文内容
*/
public interface Content extends Sendable {
//内容的类型
String type();
//在内容还没有准备之前,即还没有调用prepare()方法之前,length()方法返回-1。
long length();
}
4.FileContent.java
package Test;
import java.io.*;
import java.net.*;
import java.nio.channels.*;
import java.nio.charset.*;
/*文件形式的响应正文*/
public class FileContent implements Content {
//假定文件的根目录为"root"
private static File ROOT = new File("D:\\");
private File file;
public FileContent(URI uri) {
file = new File(ROOT,
uri.getPath()
.replace('/',File.separatorChar));
}
private String type = null;
/* 确定文件类型 */
public String type() {
if (type != null) return type;
String nm = file.getName();
if (nm.endsWith(".html")|| nm.endsWith(".htm"))
type = "text/html; charset=GBK"; //HTML网页
else if ((nm.indexOf('.') < 0) || nm.endsWith(".txt"))
type = "text/plain; charset=GBK"; //文本文件
else
type = "application/octet-stream"; //应用程序
return type;
}
private FileChannel fileChannel = null;
private long length = -1; //文件长度
private long position = -1; //文件的当前位置
public long length() {
return length;
}
/* 创建FileChannel对象*/
public void prepare() throws IOException {
if (fileChannel == null)
fileChannel = new RandomAccessFile(file, "r").getChannel();
length = fileChannel.size();
position = 0;
}
/* 发送正文,如果发送完毕,就返回false,否则返回true */
public boolean send(ChannelIO channelIO) throws IOException {
if (fileChannel == null)
throw new IllegalStateException();
if (position < 0)
throw new IllegalStateException();
if (position >= length) {
return false; //如果发送完毕,就返回false
}
position += channelIO.transferTo(fileChannel, position, length - position);
return (position < length);
}
public void release() throws IOException {
if (fileChannel != null){
fileChannel.close(); //关闭fileChannel
fileChannel = null;
}
}
}
5.Hanlder.java
<span style="font-size:14px;"><strong>package Test;
import java.io.*;
import java.nio.channels.*;
public interface Handler {
public void handle(SelectionKey key) throws IOException;
}
</strong></span>
6.HttpServer.java
package Test;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
public class HttpServer{
private Selector selector = null;
private ServerSocketChannel serverSocketChannel = null;
private int port = 80;
private Charset charset=Charset.forName("GBK");
public HttpServer()throws IOException{
selector = Selector.open();
serverSocketChannel= ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
System.out.println("服务器启动");
}
public void service() throws IOException{
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT,new AcceptHandler());
for(;;){
int n = selector.select();
if(n==0)continue;
Set readyKeys = selector.selectedKeys();
Iterator it = readyKeys.iterator();
while (it.hasNext()){
SelectionKey key=null;
try{
key = (SelectionKey) it.next();
it.remove();
final Handler handler = (Handler)key.attachment();
handler.handle(key);
}catch(IOException e){
e.printStackTrace();
try{
if(key!=null){
key.cancel();
key.channel().close();
}
}catch(Exception ex){e.printStackTrace();}
}
}//#while
}//#while
}
public static void main(String args[])throws Exception{
final HttpServer server = new HttpServer();
server.service();
}
}
7.MalformedRequestException.java
<span style="font-size:14px;"><strong>package Test;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
public class HttpServer{
private Selector selector = null;
private ServerSocketChannel serverSocketChannel = null;
private int port = 80;
private Charset charset=Charset.forName("GBK");
public HttpServer()throws IOException{
selector = Selector.open();
serverSocketChannel= ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
System.out.println("服务器启动");
}
public void service() throws IOException{
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT,new AcceptHandler());
for(;;){
int n = selector.select();
if(n==0)continue;
Set readyKeys = selector.selectedKeys();
Iterator it = readyKeys.iterator();
while (it.hasNext()){
SelectionKey key=null;
try{
key = (SelectionKey) it.next();
it.remove();
final Handler handler = (Handler)key.attachment();
handler.handle(key);
}catch(IOException e){
e.printStackTrace();
try{
if(key!=null){
key.cancel();
key.channel().close();
}
}catch(Exception ex){e.printStackTrace();}
}
}//#while
}//#while
}
public static void main(String args[])throws Exception{
final HttpServer server = new HttpServer();
server.service();
}
}
</strong></span>
8.Request.java
<strong><span style="font-size:14px;">package Test;
import java.net.*;
import java.nio.*;
import java.nio.charset.*;
import java.util.regex.*;
/* 代表客户的HTTP请求 */
public class Request {
static class Action { //枚举类,表示HTTP请求方式
private String name;
private Action(String name) { this.name = name; }
public String toString() { return name; }
static Action GET = new Action("GET");
static Action PUT = new Action("PUT");
static Action POST = new Action("POST");
static Action HEAD = new Action("HEAD");
public static Action parse(String s) {
if (s.equals("GET"))
return GET;
if (s.equals("PUT"))
return PUT;
if (s.equals("POST"))
return POST;
if (s.equals("HEAD"))
return HEAD;
throw new IllegalArgumentException(s);
}
}
private Action action;
private String version;
private URI uri;
public Action action() { return action; }
public String version() { return version; }
public URI uri() { return uri; }
private Request(Action a, String v, URI u) {
action = a;
version = v;
uri = u;
}
public String toString() {
return (action + " " + version + " " + uri);
}
private static Charset requestCharset = Charset.forName("GBK");
/* 判断ByteBuffer是否包含了HTTP请求的所有数据。
* HTTP请求以“\r\n\r\n”结尾。
*/
public static boolean isComplete(ByteBuffer bb) {
ByteBuffer temp=bb.asReadOnlyBuffer();
temp.flip();
String data=requestCharset.decode(temp).toString();
if(data.indexOf("\r\n\r\n")!=-1){
return true;
}
return false;
}
/*
* 删除请求正文,本例子仅支持GET和HEAD请求方式,忽略HTTP请求中的正文部分
*/
private static ByteBuffer deleteContent(ByteBuffer bb) {
ByteBuffer temp=bb.asReadOnlyBuffer();
String data=requestCharset.decode(temp).toString();
if(data.indexOf("\r\n\r\n")!=-1){
data=data.substring(0,data.indexOf("\r\n\r\n")+4);
return requestCharset.encode(data);
}
return bb;
}
/*
* 设定用于解析HTTP请求的字符串匹配模式。对于以下形式的HTTP请求:
*
* GET /dir/file HTTP/1.1
* Host: hostname
*
* 将被解析成:
*
* group[1] = "GET"
* group[2] = "/dir/file"
* group[3] = "1.1"
* group[4] = "hostname"
*/
private static Pattern requestPattern
= Pattern.compile("\\A([A-Z]+) +([^ ]+) +HTTP/([0-9\\.]+)$"
+ ".*^Host: ([^ ]+)$.*\r\n\r\n\\z",
Pattern.MULTILINE | Pattern.DOTALL);
/* 解析HTTP请求,创建相应的Request对象 */
public static Request parse(ByteBuffer bb) throws MalformedRequestException {
bb=deleteContent(bb); //删除请求正文
CharBuffer cb = requestCharset.decode(bb); //解码
Matcher m = requestPattern.matcher(cb); //进行字符串匹配
//如果HTTP请求与指定的字符串模式不匹配,说明请求数据不正确
if (!m.matches())
throw new MalformedRequestException();
Action a;
try { //获得请求方式
a = Action.parse(m.group(1));
} catch (IllegalArgumentException x) {
throw new MalformedRequestException();
}
URI u;
try { //获得URI
u = new URI("http://"
+ m.group(4)
+ m.group(2));
} catch (URISyntaxException x) {
throw new MalformedRequestException();
}
//创建一个Request对象,并将其返回
return new Request(a, m.group(3), u);
}
}
</span></strong>
9.RequestHanlder..java
<strong><span style="font-size:14px;">package Test;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class RequestHandler implements Handler {
private ChannelIO channelIO;
private ByteBuffer requestByteBuffer = null; //存放HTTP请求的缓冲区
private boolean requestReceived = false; //是否已经接收到了所有的HTTP请求
private Request request = null; //表示HTTP请求
private Response response = null; //表示HTTP响应
RequestHandler(ChannelIO channelIO) {
this.channelIO = channelIO;
}
/*
* 接收HTTP请求,如果已经接收到了所有的HTTP请求数据,就返回true,否则返回false
*/
private boolean receive(SelectionKey sk) throws IOException {
ByteBuffer tmp = null;
if (requestReceived)return true; //如果已经接收到所有HTTP请求数据,返回true
//如果已经读到通道的末尾,或者已经读到HTTP请求数据的末尾标志“\r\n”,就返回true
if ((channelIO.read() < 0) || Request.isComplete(channelIO.getReadBuf())) {
requestByteBuffer = channelIO.getReadBuf();
return (requestReceived = true);
}
return false;
}
/*
* 通过Request类的parse()方法,解析requestByteBuffer中的HTTP请求数据,构造相应的Request对象
*/
private boolean parse() throws IOException {
try {
request = Request.parse(requestByteBuffer);
return true;
} catch (MalformedRequestException x) {
//如果HTTP请求的格式不正确,就发送错误信息
response = new Response(Response.Code.BAD_REQUEST,
new StringContent(x));
}
return false;
}
/*
* 创建HTTP响应
*/
private void build() throws IOException {
Request.Action action = request.action();
//仅仅支持GET和HEAD请求方式
if ((action != Request.Action.GET) &&
(action != Request.Action.HEAD)){
response = new Response(Response.Code.METHOD_NOT_ALLOWED,
new StringContent("Method Not Allowed"));
}else{
response = new Response(Response.Code.OK,
new FileContent(request.uri()), action);
}
}
/* 接收HTTP请求,发送HTTP响应 */
public void handle(SelectionKey sk) throws IOException {
try {
if (request == null) { //如果还没有接收到HTTP请求的所有数据
//接收HTTP请求
if (!receive(sk))return;
requestByteBuffer.flip();
//如果成功解析了HTTP请求,就创建一个Response对象
if (parse())build();
try {
response.prepare(); //准备HTTP响应的内容
} catch (IOException x) {
response.release();
response = new Response(Response.Code.NOT_FOUND,
new StringContent(x));
response.prepare();
}
if (send()) {
//如果HTTP响应没有发送完毕,则需要注册写就绪事件,
//以便在写就绪事件发生时继续发送数据
sk.interestOps(SelectionKey.OP_WRITE);
} else {
//如果HTTP响应发送完毕,就断开底层的连接,并且释放Response占用的资源
channelIO.close();
response.release();
}
} else { //如果已经接收到HTTP请求的所有数据
if (!send()) { //如果HTTP响应发送完毕
channelIO.close();
response.release();
}
}
} catch (IOException x) {
x.printStackTrace();
channelIO.close();
if (response != null) {
response.release();
}
}
}
/* 发送HTTP响应,如果全部发送完毕,就返回false,否则返回true */
private boolean send() throws IOException {
return response.send(channelIO);
}
}
</span></strong>
10.Response.java
<strong><span style="font-size:14px;">package Test;
import java.io.*;
import java.nio.*;
import java.nio.charset.*;
public class Response implements Sendable {
static class Code { //枚举类,表示状态代码
private int number;
private String reason;
private Code(int i, String r) { number = i; reason = r; }
public String toString() { return number + " " + reason; }
static Code OK = new Code(200, "OK");
static Code BAD_REQUEST = new Code(400, "Bad Request");
static Code NOT_FOUND = new Code(404, "Not Found");
static Code METHOD_NOT_ALLOWED = new Code(405, "Method Not Allowed");
}
private Code code; //状态代码
private Content content; //响应正文
private boolean headersOnly; //表示HTTP响应中是否仅包含响应头
private ByteBuffer headerBuffer = null; //响应头
public Response(Code rc, Content c) {
this(rc, c, null);
}
public Response(Code rc, Content c, Request.Action head) {
code = rc;
content = c;
headersOnly = (head == Request.Action.HEAD);
}
private static String CRLF = "\r\n";
private static Charset responseCharset = Charset.forName("GBK");
/* 创建响应头的内容,把它存放到一个ByteBuffer中 */
private ByteBuffer headers() {
CharBuffer cb = CharBuffer.allocate(1024);
for (;;) {
try {
cb.put("HTTP/1.1 ").put(code.toString()).put(CRLF);
cb.put("Server: nio/1.1").put(CRLF);
cb.put("Content-type: ").put(content.type()).put(CRLF);
cb.put("Content-length: ")
.put(Long.toString(content.length())).put(CRLF);
cb.put(CRLF);
break;
} catch (BufferOverflowException x) {
assert(cb.capacity() < (1 << 16));
cb = CharBuffer.allocate(cb.capacity() * 2);
continue;
}
}
cb.flip();
return responseCharset.encode(cb); //编码
}
/* 准备HTTP响应中的正文以及响应头的内容 */
public void prepare() throws IOException {
content.prepare();
headerBuffer= headers();
}
/* 发送HTTP响应,如果全部发送完毕,返回false,否则返回true */
public boolean send(ChannelIO cio) throws IOException {
if (headerBuffer == null)
throw new IllegalStateException();
//发送响应头
if (headerBuffer.hasRemaining()) {
if (cio.write(headerBuffer) <= 0)
return true;
}
//发送响应正文
if (!headersOnly) {
if (content.send(cio))
return true;
}
return false;
}
/* 释放响应正文占用的资源 */
public void release() throws IOException {
content.release();
}
}
</span></strong>
11.Sendable.java
<strong><span style="font-size:14px;">package Test;
import java.io.*;
/*
*表示服务器可以发送给客户端的东西
*/
public interface Sendable {
// 准备发送的内容
public void prepare() throws IOException;
// 利用通道发送部分内容,如果所有内容发送完毕,就返回false
// 如果还有内容未发送,就返回true
// 如果内容还没有准备好,就抛出IllegalStateException
public boolean send(ChannelIO cio) throws IOException;
//当服务器发送内容完毕,就调用此方法,释放内容占用的资源
public void release() throws IOException;
}
</span></strong>
12.SimpleHttpServer.java
<strong><span style="font-size:14px;">package Test;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
public class SimpleHttpServer {
private int port=80;
private ServerSocketChannel serverSocketChannel = null;
private ExecutorService executorService;
private static final int POOL_MULTIPLE = 4;
public SimpleHttpServer() throws IOException {
executorService= Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * POOL_MULTIPLE);
serverSocketChannel= ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
System.out.println("服务器启动");
}
public void service() {
while (true) {
SocketChannel socketChannel=null;
try {
socketChannel = serverSocketChannel.accept();
executorService.execute(new Handler(socketChannel));
}catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String args[])throws IOException {
new SimpleHttpServer().service();
}
class Handler implements Runnable{
private SocketChannel socketChannel;
public Handler(SocketChannel socketChannel){
this.socketChannel=socketChannel;
}
public void run(){
handle(socketChannel);
}
public void handle(SocketChannel socketChannel){
try {
Socket socket=socketChannel.socket();
System.out.println("接收到客户连接,来自: " +
socket.getInetAddress() + ":" +socket.getPort());
ByteBuffer buffer=ByteBuffer.allocate(1024);
socketChannel.read(buffer);
buffer.flip();
String request=decode(buffer);
System.out.print(request); //打印HTTP请求
//输出HTTP响应结果
StringBuffer sb=new StringBuffer("HTTP/1.1 200 OK\r\n");
sb.append("Content-Type:text/html\r\n\r\n");
socketChannel.write(encode(sb.toString()));//输出响应头
FileInputStream in;
//获得HTTP请求的第一行
String firstLineOfRequest=request.substring(0,request.indexOf("\r\n"));
if(firstLineOfRequest.indexOf("login.htm")!=-1)
in=new FileInputStream("root/login.htm");
else
in=new FileInputStream("root/hello.htm");
FileChannel fileChannel=in.getChannel();
fileChannel.transferTo(0,fileChannel.size(),socketChannel);
fileChannel.close();
}catch (Exception e) {
e.printStackTrace();
}finally {
try{
if(socketChannel!=null)socketChannel.close();
}catch (IOException e) {e.printStackTrace();}
}
}
private Charset charset=Charset.forName("GBK");
public String decode(ByteBuffer buffer){ //解码
CharBuffer charBuffer= charset.decode(buffer);
return charBuffer.toString();
}
public ByteBuffer encode(String str){ //编码
return charset.encode(str);
}
}
}
</span></strong>
13.StringContent.java
<strong><span style="font-size:14px;">package Test;
import java.io.*;
import java.nio.*;
import java.nio.charset.*;
/* 字符串形式的内容 */
public class StringContent implements Content {
private static Charset charset = Charset.forName("GBK");
private String type; // MIME type
private String content;
public StringContent(CharSequence c, String t) {
content = c.toString();
if (!content.endsWith("\n"))
content += "\n";
type = t + "; charset=GBK";
}
public StringContent(CharSequence c) {
this(c, "text/plain");
}
public StringContent(Exception x) {
StringWriter sw = new StringWriter();
x.printStackTrace(new PrintWriter(sw));
type = "text/plain; charset=GBK";
content = sw.toString();
}
public String type() {
return type;
}
private ByteBuffer bb = null;
private void encode() {
if (bb == null)
bb = charset.encode(CharBuffer.wrap(content));
}
public long length() {
encode();
return bb.remaining();
}
public void prepare() {
encode();
bb.rewind();
}
public boolean send(ChannelIO cio) throws IOException {
if (bb == null)
throw new IllegalStateException();
cio.write(bb);
return bb.hasRemaining();
}
public void release() throws IOException {}
}
</span></strong>