如何用Java编写你自己的简单HTTP服务器
如何用Java编写你自己的简单HTTP服务器,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
HTTP是个大协议,完整功能的HTTP服务器必须响应资源请求,将URL转换为本地系统的资源名。响应各种形式的HTTP请求(GET、POST等)。处理不存在的文件请求,返回各种形式的状态码,解析MIME类型等。但许多特定功能的HTTP服务器并不需要所有这些功能。例如,很多网站只是想显示"建设中"的消息。很显然,Apache对于这样的网站是大材小用了。这样的网站完全可以使用只做一件事情的定制服务器。Java网络类库使得编写这样的单任务服务器轻而易举。
定制服务器不只是用于小网站。大流量的网站如Yahoo,也使用定制服务器,因为与一般用途的服务器相比,只做一件事情的服务器通常要快得多。针对某项任务来优化特殊用途的服务器很容易;其结果往往比需要响应很多种请求的一般用途服务器高效得多。例如,对于重复用于多页面或大流量页面中的图标和图片,用一个单独的服务器处理会更好(并且还可以避免在请求时携带不必要的Cookie,因而可以减少请求/响应数据,从而减少下载带宽,提升速度);这个服务器在启动时把所有图片文件读入内存,从RAM中直接提供这些文件,而不是每次请求都从磁盘上读取。此外,如果你不想在包含这些图片的页面请求之外单独记录这些图片,这个单独服务器则会避免在日志记录上浪费时间。
简单的单文件服务器
该服务器的功能:无论接受到何种请求,都始终发送同一个文件。这个服务器命名为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对象,开始运行。这是唯一可能的接口。
下面是测试的结果:
命令行编译代码并设置参数:
telnet:
首先,启用telnet服务(如果不会,自行google之),接着测试该主机的端口:
结果(可以看到请求的输出内容):
HTTP协议测试:
文档(这是之前一篇文章--小车动画的文档):
重定向服务器
实现的功能——将用户从一个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("Document moved \r\n"); out.write("Document moved
\r\n"); out.write("The document "+theFile +" has moved to \r\n" +newSite+theFile +".\r\n Please update your bookmarks"); out.write("