程序基本流程如下:

代码组织结构如下:

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. 在linux中使用find

    最近用到了在linux系统中查找文件这个任务,学习了下面两篇文章,尤其是过滤结果信息的小技巧.记录下来,省得忘了再找. http://www.360doc.com/content/10/1110/10 ...

  2. Can jxta be used to develop online card game (p2p style)?

    Can jxta be used to develop online card game (p2p style)? https://www.java.net//node/677134 I am new ...

  3. 浏览器插件-ActiveX

    浏览器插件:B/S模式下通过在客户端浏览器安装插件调用外设或者处理特殊格式数据. 常用插件有身份证阅读器.sim卡阅读器.银行卡校验插件.手写板插件.小键盘插件: 处理表格数据的华表插件.图片合成插件 ...

  4. 利用管道实现Shell多进程

    shell中有个&,表示该程序在后台执行,其实是fork了一个子进程,跟系统调用是一样的. 在实际的操作过程中,有时需要控制后台程序的个数,毕竟启动太多的后台,会对服务的性能造成影响. 所以需 ...

  5. 宏_CRTIMP分析

    CRTIMP是C run time implement的简写,C运行库的实现的意思. 作为用户代码,不应该使用这个东西. 该参数决定 运行时 到底用 动态链接库 还是静态链接 #ifndef _CRT ...

  6. BestCoder Round #71 (div.2) (hdu 5620 菲波那切数列变形)

    KK's Steel Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total ...

  7. Javascript继承实现

    S1:js中一切皆对象,想想如果要实现对父对象属性和方法的继承,最初我们会怎样子来实现呢,考虑到原型的概念,最初我是这样来实现继承的 function Parent(){ this.name='123 ...

  8. vs2012下安装VisualHG

    好久没写东西了.懒了.最近开发用到HG,记录一下.希望对用这个的有用 http://visualhg.codeplex.com/ 这里下载 VisualHG 安装完默认情况下 源代码管理出不来Visu ...

  9. Swift学习笔记二

    Swift是苹果公司开发的一门新语言,它当然具备面向对象的许多特性,现在开始介绍Swift中类和对象的语法. 对象和类 用"class"加上类名字来创建一个类,属性声明和声明常量或 ...

  10. DynamoDB Local for Desktop Development

    Would you like to be able to write and test code that uses the Amazon DynamoDB API even if you have ...