本文共 21550 字,大约阅读时间需要 71 分钟。
最近在学习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
package Test;import java.io.*;import java.nio.channels.*;public interface Handler { public void handle(SelectionKey key) throws IOException;}
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
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(); }}
8.Request.java 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); }}
9.RequestHanlder..java 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); }}
10.Response.java 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(); }}
11.Sendable.java 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;}
12.SimpleHttpServer.java 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); } }}
13.StringContent.java 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 {}}