首先举一个简单的例子, 这个例子说明了HTTP协议的工作方式:
假设有一个人(这个人就是浏览器), 这个人需要跟外部联系, 以获取自己需要的东西,
这个人跟外部的每一个人联系都必须通过信件的方式, 首先他在信封上写明对方的地址, 对方的姓名, 还有自己的地址和姓名
其中自己的地址就代表了URL中的域名, 姓名代表了端口号. 自己需要的东西就是具体的URL。
然后他把这封信自己投递到对方的信箱中. 当对方收到信件的时候会根据信封上的地址和姓名,把需要的物品投递到这个人的信箱中.
其中信封就对应了HTTP协议. 我们来看一个HTTP协议的内容:
GET http://www.mytest.com:6666/index.html HTTP/1.1
Accept: */*
Accept-Language: zh-cn
User-Agent: mytest
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: www.mytest.com:6666
上面就相当于发信人在信封上写的内容, 第一行写的是对方的地址, 还有自己需要的东西, 另外这个人还在信封上写了这么以个内容:HTTP/1.1
这个意思是说:亲,这个信封上的内容是按照我们一开始商量好的1.1的版本写的哦。
Accept: */* // 亲,发什么东西我都可以识别哦。
Accept-Language: zh-cn // 亲,回信记得用中文哦。
User-Agent: mytest // 我当前的环境状况
Accept-Encoding: gzip, deflate // 亲,你可以使用中通和申通还有普邮哦,其他的我收不了哦。
Connection: Keep-Alive // 来了先别走哦,我可能还有东西发给你哦。
Host: www.mytest.com:6666 // 亲,这是我的地址哦。
上面是HTTP协议中请求的过程, 回复的过程跟这个差不多就不多说了。
接下来我们说代理的过程.
现在我们希望当这个人跟外部发信的时候,不需要他自己跑过去发,而是有我们代理发出去,假设我们是邮局吧。
这个人以后发信的时候不管给谁发,都把信直接投到邮局,然后他就不用管了,当邮局拿到信件以后根据信封上的地址发给接收人,然后再把接收人的响应收回来发给发信人. HTTP代理充当的就是邮局的功能。
接下来我们先用一个最简单的代码来实现一个http代理, 这个代码要正确跑起来,需要一个软件Fiddler,
为什么要用Fiddler呢,因为我们这个小邮局只有一个工作人员,这个工作人员不识字,看不懂信封上写的是什么,
他只能把这个信件转交给更专业的邮局Fiddler. 此处我们只讲转发的过程, 因此可以不用理会Fiddler.
最重要的是不要被Fiddler影响接下来的过程。
先上代码:
第一步,打开一个SocketServer, 监听指定的端口, 当有请求到达的时候起一个新的线程响应为了尽可能的说明关键逻辑,我去掉了异常捕获和资源释放的代码
[java]
socketServer = new ServerSocket(PORT);
while(true)
{
// 我们这个工作人员在很努力的等待用户的到来
// 他甚至从来不休息, 要让他停止工作的唯一方法就是kill掉他
final Socket socket = socketServer.accept();
Runnable job = new Runnable(){
public void run(){
// 终于有用户来投信了, 开始工作
service(socket);
}
};
threadPoolExecutor.execute(job);
}
然后我们来看service方法:
[java]
socket.setSoTimeout(10000);
socket.setKeepAlive(false);
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
Socket remote = new Socket("127.0.0.1", 8888);
InputStream remoteInputStream = remote.getInputStream();
OutputStream remoteOutputStream = remote.getOutputStream();
// 把信发出去
copy(inputStream, remoteOutputStream, 4096);
remote.setSoTimeout(10000);
// 把对方的响应发给发信人
copy(remoteInputStream, outputStream, 4096);
service方法很简单, 核心代码只有10行, 整体来看,核心代码不足20行, 但是它确是可以运行的, 并且很正常.
当然性能很差, 打开一个复杂的页面, 需要好几分钟. 但是用来说明http代理的工作方式, 却很容易明白.
在下一篇文章中我们再进一步扩展,增加这个工作人员的能力,现在他还很笨。
下面是完整的代码:
[java]
/*
* $RCSfile: SimpleHttpProxy1.java,v $$
* $Revision: 1.1 $
* $Date: 2013-1-14 $
*
* Copyright (C) 2008 Skin, Inc. All rights reserved.
*
* This software is the proprietary information of Skin, Inc.
* Use is subject to license terms.
*/
package test.com.skin.http.proxy;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* <p>Title: SimpleHttpProxy1</p>
* <p>Description: </p>
* <p>Copyright: Copyright (c) 2006</p>
* @author xuesong.net
* @version 1.0
*/
public class SimpleHttpProxy1
{
public static final int PORT = 6666;
public static final byte[] CRLF = new byte[]{0x0D, 0x0A};
/**
* @param args
*/
public static void main(String[] args)
{
ServerSocket socketServer = null;
BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(1024);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(512, 1024, 30000, TimeUnit.SECONDS, blockingQueue);
try
{
socketServer = new ServerSocket(PORT);
while(true)
{
try
{
final Socket socket = socketServer.accept();
Runnable job = new Runnable(){
public void run(){