程序基本流程如下:

代码组织结构如下:

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响应报文格式参照http://blog.csdn.net/a19881029/article/details/14002273

在解包时进行循环读取,以避免请求接收端没有接收到完整的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转码,参见http://blog.csdn.net/a19881029/article/details/18262671

由于设置了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重定向服务器的更多相关文章

  1. 前端学HTTP之重定向和负载均衡

    前面的话 HTTP并不是独自运行在网上的.很多协议都会在HTTP报文的传输过程中对其数据进行管理.HTTP只关心旅程的端点(发送者和接收者),但在包含有镜像服务器.Web代理和缓存的网络世界中,HTT ...

  2. http status 301/302 & java重定向/转发

    一.301/3021.什么是301转向?什么是301重定向? 301转向(或叫301重定向,301跳转)是当用户或搜索引擎向网站服务器发出浏览请求时,服务器返回的HTTP数据流中头信息(header) ...

  3. 重定向和servlet生命周期

    重定向(1)什么是重定向服务器通知浏览器向一个新的地址发送请求.注:可以发送一个302状态码和一个Location消息头.(该消息头包含了一个地址,称之为重定向地址),浏览器收到之后,会立即向重定向地 ...

  4. 2.Servlet 请求、响应及重定向

    PS:以下仅为个人学习笔记,涩及方面略窄  #######################     Request     ####################### /** *    reque ...

  5. 和我一起学《HTTP权威指南》——Web服务器

    Web服务器 Web服务器会做些什么 1.建立连接(接受或关闭一个客户端连接) 2.接收请求(读取HTTP报文) 3.处理请求(解释请求报文并采取行动) 4.访问资源 5.构建响应(创建带有正确首部的 ...

  6. 负载均衡之HTTP重定向

    转载请说明出处:http://blog.csdn.net/cywosp/article/details/38014581 由于目前现有网络的各个核心部分随着业务量的提高,访问量和数据流量的快速增长,其 ...

  7. Servlet学习笔记04——什么是重定向,servlet生命周期?

    1.重定向 (1)什么是重定向? 服务器通知浏览器访问一个新的地址. 注: 服务器可以通过发送一个302状态码及一个 Location消息头(该消息头的值是一个地址,一般 称之为重定向地址)给浏览器, ...

  8. SIP协议&开源SIP服务器搭建和客户端安装

    1. SIP SIP 是一个应用层的控制协议,可以用来建立,修改,和终止多媒体会话,例如Internet电话 SIP在建立和维持终止多媒体会话协议上,支持五个方面: 1)   用户定位: 检查终端用户 ...

  9. [TimLinux] JavaScript AJAX如何重定向页面

    1. AJAX 异步JavaScript + XML,用于不通过页面from表单,来发送数据到后端服务器中 2. 如何重定向 服务器后端无法直接将页面重定向,因为服务器后端传回的任何数据,都将被XML ...

随机推荐

  1. j2ee的十三个规范

    转载 今天在做连接oracle数据库的时候,感受到了什么是规范.平时听到别人说学习j2ee一定要学习他的十三个规范,大概的知道每个规范是做什么的,每个“接口”是做什么的.          很早就听过 ...

  2. coco2d-x 纹理研究

    转自:http://blog.csdn.net/qq51931373/article/details/9119161 1.通常情况下用PVR格式的文件来进行图片显示的时候,在运行速度和内存消耗方面都要 ...

  3. JavaFX 2 Dialogs

    http://edu.makery.ch/blog/2012/10/30/javafx-2-dialogs/ ———————————————————————————————————————————— ...

  4. HDU 2874 Connections between cities (LCA)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2874 题意是给你n个点,m条边(无向),q个询问.接下来m行,每行两个点一个边权,而且这个图不能有环路 ...

  5. ZendFramework2 与MongoDB的整合

    从网上找了很多文章,先是直接搜关键字找zf2与mongoDB的文章,然后回到源头先学习了一下mongoDB是什么,以及纯PHP环境下怎么用,又从github上找了几个mongoDB的zf2模块,还FQ ...

  6. socket断开连接的四次握手及常见过程解析

    TCP的协议文档对TCP的一些规定:文档名称-RFC793  TCP消息头的控制位 URG:紧急指针字段有效 ACK:确认头部字段有效 PSH:强制函数 RST:重置链接 SYN:同步系列号码 FIN ...

  7. Squid 日志详解

    原文地址: http://www.php-oa.com/2008/01/17/squid-log-access-store.html access.log 日志 在squid中access访问日志最为 ...

  8. delphi 取得汉字的第一个字母

    功能说明://取得汉字的第一个字母 function GetPYIndexChar( hzchar:string):char;begin  caseWORD(hzchar[1])shl8+WORD(h ...

  9. linux 下网站压力测试工具webbench

    一直在用webbench ,这个linux下的网站压力测试工具.整理下. 笔记本装的ubuntu,其他linux系统也差不多. webbench 需要先安装 ctags,一个vim的阅读插件,可以直接 ...

  10. 语法:MySQL中INSERT INTO SELECT的使用(转)

    1. 语法介绍      有三张表a.b.c,现在需要从表b和表c中分别查几个字段的值插入到表a中对应的字段.对于这种情况,可以使用如下的语句来实现: INSERT INTO db1_name (fi ...