HTTP重定向服务器
2014-04-16 16:16
357 查看
程序基本流程如下:
代码组织结构如下:
HTTP重定向服务主线程:
HTTP请求封装类:
HTTP请求报文格式和HTTP响应报文格式参照/article/1515485.html
在解包时进行循环读取,以避免请求接收端没有接收到完整的HTTP请求报文信息
\n占一个字节,字节值为10,\r也占一个字节,字节值为13
HTTP响应封装类:
HTTP请求处理线程:
需要注意的是,这里并没有针对中文域名进行处理,也就是系统并不支持中文域名,如果需要支持中文域名,需要进行punycode转码,参见/article/1515473.html
由于设置了socket.setSoLinger(true, 0),当调用socket.close()方法时,底层socket连接会立即关闭,此时HTTP响应结果有可能还未全部发送完毕,故在关闭socket连接前,处理线程休眠200毫秒以便底层socket有一段时间用来发送HTTP响应信息
配置文件读取类:
HTTP请求的默认监听端口为80
日志配置文件log4j.properties:
HttpServerLog:记录HTTP重定向服务主线程的运行状况
ThreadLog:记录每一个HTTP请求处理线程的运行状况
RequestNum:记录系统负载情况
系统参数配置文件config.xml:
这些参数需要根据服务器配置、系统负载进行配置,以便程序达到最佳性能
通过HttpServer类启动服务,此时在浏览器中输入http://localhost,页面最终将被重定向至http://www.baidu.com
代码组织结构如下:
HTTP重定向服务主线程:
package com.server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.log4j.Logger; import com.conf.Config; public class HttpServer implements Runnable { private static ServerSocket server_socket = null; private static ExecutorService pool; private static int requestNum = 0; private static Logger serverLog = Logger.getLogger("HttpServerLog"); private static Logger requestNumLog = Logger.getLogger("RequestNumber"); public void run() { startServer(Config.serverListenPort); } private void startServer(int port){ try { pool = Executors.newFixedThreadPool(Config.threadPoolSize); server_socket = new ServerSocket(port,Config.serverQueueSize); serverLog.info("HTTP Server starts on port:" + server_socket.getLocalPort()); while (true) { try { if(Config.curThreadsNum.get() >= Config.maxThreadsNum.get()){ serverLog.info("HTTP Server sleep for 1 second!"); Thread.sleep(1000); continue; } } catch (Exception e) { serverLog.error(e); continue; } serverLog.debug("Get client request!"); Socket socket = server_socket.accept(); serverLog.debug("Create socket successfully!"); //socket.setReuseAddress(true); //某些HTTP客户端建立连接后不发送数据 //如果这种连接过多,系统线程将被耗尽 //所以必须设置连接超时时间 socket.setSoTimeout(2*1000); socket.setSoLinger(true, 0); serverLog.debug("New connection:" + socket.getInetAddress() + ":" + socket.getPort()); serverLog.info("Max:" + Config.maxThreadsNum + ";Cur:" + Config.curThreadsNum); requestNum++; if(requestNum > 10000){ requestNumLog.info("10000 requests"); requestNum = 0; } try { DealThread dt = new DealThread(socket); serverLog.debug("Deal thread create successfully!"); pool.execute(dt); Config.curThreadsNum.incrementAndGet(); } catch (Exception e) { serverLog.error(e); } } } catch (IOException e) { serverLog.error(e); } } public static void main(String[] args){ HttpServer hs = new HttpServer(); Thread t = new Thread(hs); t.start(); } }
HTTP请求封装类:
HTTP请求报文格式和HTTP响应报文格式参照/article/1515485.html
在解包时进行循环读取,以避免请求接收端没有接收到完整的HTTP请求报文信息
\n占一个字节,字节值为10,\r也占一个字节,字节值为13
package com.request; import java.io.InputStream; import java.net.Socket; import java.util.ArrayList; import com.server.DealThread; public class Request { private InputStream input; private String headerString = ""; private String bodyString = ""; public Request(Socket socket) throws Exception{ this.input = socket.getInputStream(); DealThread.threadLog.debug("Thread[" + Thread.currentThread().getId() + "]get input stream"); } public void resolvePackage(Socket socket) throws Exception{ DealThread.threadLog.debug("Thread[" + Thread.currentThread().getId() + "]analysis package begin"); String line = null; // HTTP request body length int contentLength = 0; // get HTTP request head do { line = readLine(input, 0); if (line.startsWith("Content-Length")) { contentLength = Integer.parseInt(line.split(":")[1].trim()); } headerString += line; //如果遇到了一个单独的回车换行,则表示请求头结束 } while (!line.equals("\r\n")); if(contentLength != 0){ bodyString = readLine(input,contentLength); } DealThread.threadLog.debug("HTTP request head:" + headerString); DealThread.threadLog.debug("HTTP request body:" + bodyString); DealThread.threadLog.debug("Thread[" + Thread.currentThread().getId() + "]analysis package end"); } private String readLine(InputStream is, int contentLe) throws Exception { ArrayList<Byte> lineByteList = new ArrayList<Byte>(); byte[] readByte; byte b; if (contentLe != 0 && contentLe > 0) { readByte = new byte[contentLe]; int num = 0; int totalnum = contentLe; int realreadnum = 0; while(num < totalnum){ realreadnum = is.read(readByte, num, totalnum-num); if(realreadnum > 0){ num += realreadnum; }else{ break; } } return new String(readByte); } else { //读请求头 do { b = (byte)is.read(); lineByteList.add(Byte.valueOf(b)); } while (b != 10); byte[] tmpByteArr = new byte[lineByteList.size()]; for (int i = 0; i < lineByteList.size(); i++) { tmpByteArr[i] = lineByteList.get(i).byteValue(); } lineByteList.clear(); return new String(tmpByteArr); } } public String getHeader(String name) { if (name == null || name.equals("")) return null; name = name + ": "; try { String[] item = headerString.split("\n"); String headerLine = null; for(int i=0;i<item.length;i++){ headerLine = item[i]; if (headerLine.indexOf(name) == 0) { return headerLine.substring(name.length()); } } } catch (Exception e) { DealThread.threadLog.error(e); } return null; } }
HTTP响应封装类:
package com.response; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import com.server.DealThread; public class Response { private OutputStream output; private String ip; public Response(Socket socket) throws Exception{ this.output = socket.getOutputStream(); DealThread.threadLog.debug("Thread[" + Thread.currentThread().getId() + "]get output stream"); ip = socket.getInetAddress().toString(); } public void sendRedirect(String redirectUrl) { String head = "HTTP/1.1 200 OK\r\n" + "Content-Type:text/html\r\n"; String body = "<html><SCRIPT type=text/javascript>" + "window.location.href=\"" + redirectUrl + "\";</script></html>"; head += "Content-length:"+body.getBytes().length+"\r\n\r\n"; try { output.write(head.getBytes()); output.write(body.getBytes()); output.flush(); } catch (IOException e) { e.printStackTrace(); System.out.println("Thread[" + Thread.currentThread().getId() + "]["+ip+"]Redirect Send Error:"+redirectUrl); } } }
HTTP请求处理线程:
需要注意的是,这里并没有针对中文域名进行处理,也就是系统并不支持中文域名,如果需要支持中文域名,需要进行punycode转码,参见/article/1515473.html
由于设置了socket.setSoLinger(true, 0),当调用socket.close()方法时,底层socket连接会立即关闭,此时HTTP响应结果有可能还未全部发送完毕,故在关闭socket连接前,处理线程休眠200毫秒以便底层socket有一段时间用来发送HTTP响应信息
package com.server; import java.net.InetAddress; import java.net.Socket; import org.apache.log4j.Logger; import com.conf.Config; import com.request.Request; import com.response.Response; public class DealThread implements Runnable { private Socket socket; private Response response; private Request request; public static Logger threadLog = Logger.getLogger("ThreadLog"); public DealThread(Socket socket) throws Exception { this.socket = socket; this.request = new Request(this.socket); this.response = new Response(this.socket); } public void run() { try { threadLog.debug("thread " + Thread.currentThread().getName() + " open"); request.resolvePackage(socket); processRequest(); } catch (Exception e) { threadLog.error(e); }finally{ try { String identify = socket.getInetAddress() + ":" + socket.getLocalPort(); Thread.sleep(200); socket.shutdownInput(); socket.shutdownOutput(); socket.close(); if(socket.isClosed()){ threadLog.debug("socket [" + identify + "] closed"); } } catch (Exception e) { threadLog.error(e); } Config.curThreadsNum.decrementAndGet(); threadLog.debug("[Thread " + Thread.currentThread().getId() + "] closed"); } } private void processRequest() throws Exception { //没有对中文域名进行转码 String host = request.getHeader("Host"); String user_agent = request.getHeader("User-Agent"); InetAddress netAddress = socket.getInetAddress(); String address = netAddress.getHostAddress(); threadLog.info("HOST:" + host + " User-Agent:" + user_agent + " IP:" + address); String redirectUrl = "http://www.baidu.com"; response.sendRedirect(new String(redirectUrl.getBytes("GBK"),"ISO-8859-1")); threadLog.info("redirectUrl:"+redirectUrl); } }
配置文件读取类:
HTTP请求的默认监听端口为80
package com.conf; import java.io.File; import java.util.concurrent.atomic.AtomicInteger; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class Config { static{ SAXReader reader = new SAXReader(); Document document; try { String filePath = "./conf/config.xml"; document = reader.read(new File(filePath)); Element root = document.getRootElement(); int max_threads_num = Integer.valueOf( root.element("max_threads_num").getTextTrim()).intValue(); maxThreadsNum = new AtomicInteger(max_threads_num); int cur_threads_num = Integer.valueOf( root.element("cur_threads_num").getTextTrim()).intValue(); curThreadsNum = new AtomicInteger(cur_threads_num); serverListenPort = Integer.valueOf( root.element("server_listen_port").getTextTrim()).intValue(); serverQueueSize = Integer.valueOf( root.element("server_queue_size").getTextTrim()).intValue(); threadPoolSize = Integer.valueOf( root.element("thread_pool_size").getTextTrim()).intValue(); } catch (DocumentException e) { e.printStackTrace(); maxThreadsNum = new AtomicInteger(50); curThreadsNum = new AtomicInteger(0); serverListenPort = 80; serverQueueSize = 200; threadPoolSize = 60; } } public static AtomicInteger maxThreadsNum; public static AtomicInteger curThreadsNum; public static int serverListenPort; public static int serverQueueSize; public static int threadPoolSize; }
日志配置文件log4j.properties:
HttpServerLog:记录HTTP重定向服务主线程的运行状况
ThreadLog:记录每一个HTTP请求处理线程的运行状况
RequestNum:记录系统负载情况
log4j.rootLogger=INFO,console log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d{MM-dd HH:mm:ss}->%m%n log4j.logger.HttpServerLog=debug,HttpServerLog log4j.appender.HttpServerLog=org.apache.log4j.RollingFileAppender log4j.additivity.HttpServerLog=false log4j.appender.HttpServerLog.File=./log/HttpServer.log log4j.appender.HttpServerLog.MaxFileSize=10MB log4j.appender.HttpServerLog.MaxBackupIndex=0 log4j.appender.HttpServerLog.layout=org.apache.log4j.PatternLayout log4j.appender.HttpServerLog.layout.ConversionPattern=%d{MM-dd HH:mm:ss}->%m%n log4j.logger.ThreadLog=debug,ThreadLog log4j.appender.ThreadLog=org.apache.log4j.RollingFileAppender log4j.additivity.ThreadLog=false log4j.appender.ThreadLog.File=./log/Thread.log log4j.appender.ThreadLog.MaxFileSize=10MB log4j.appender.ThreadLog.MaxBackupIndex=0 log4j.appender.ThreadLog.layout=org.apache.log4j.PatternLayout log4j.appender.ThreadLog.layout.ConversionPattern=%d{MM-dd HH:mm:ss}[%t]->%m%n log4j.logger.RequestNumber=info,RequestNumber log4j.appender.RequestNumber=org.apache.log4j.RollingFileAppender log4j.additivity.RequestNumber=false log4j.appender.RequestNumber.File=./log/RequestNum.log log4j.appender.RequestNumber.MaxFileSize=1MB log4j.appender.RequestNumber.MaxBackupIndex=0 log4j.appender.RequestNumber.layout=org.apache.log4j.PatternLayout log4j.appender.RequestNumber.layout.ConversionPattern=%d{MM-dd HH:mm:ss}->%m%n
系统参数配置文件config.xml:
这些参数需要根据服务器配置、系统负载进行配置,以便程序达到最佳性能
<?xml version="1.0" encoding="UTF-8"?> <server_config> <!-- 最大处理线程数 --> <max_threads_num>50</max_threads_num> <!-- 当前处理线程数 --> <cur_threads_num>0</cur_threads_num> <!-- 服务监听端口 --> <server_listen_port>80</server_listen_port> <!-- 服务请求接收队列长度 --> <server_queue_size>200</server_queue_size> <!-- 处理线程线程池大小 --> <thread_pool_size>60</thread_pool_size> </server_config>
通过HttpServer类启动服务,此时在浏览器中输入http://localhost,页面最终将被重定向至http://www.baidu.com
相关文章推荐
- HTTP重定向与服务器转发的区别
- HTTP重定向服务器
- nginx服务器http重定向到https的正确写法
- nginx服务器http重定向到https的正确写法
- .net 服务器 HttpRespond.Response.Redirect重定向问题理解
- Nginx服务器http重定向到https
- nginx服务器http重定向到https的正确写法
- nginx服务器http重定向到https的正确写法
- 详解nginx服务器http重定向到https的正确写法
- nginx服务器http重定向到https的正确写法
- 服务器 nginx配置ssl并http重定向到https
- http post 协议 向服务器提交图片
- HttpServletResponse的转发和重定向
- Http协议下的浏览器与服务器的交互
- Http服务器的交互请求 GET,POST,PUT和DELETE
- Android-->原生API搭建Android Http服务器
- rtsp流媒体服务器初步选型http://hi.baidu.com/xoxoxo/item/e0f68c14b979014ae65e06c1
- ASP---HTTP 错误 403.14 - Forbidden Web 服务器被配置为不列出此目录的内容
- 使用async-http 重定向出错 & 文字没有焦点,仍实现自滚动
- 高并发下web服务器http异常状态码499/502/504分析