[TOC]
## 什么是proxy(代理)
代理(英語:Proxy),也稱網絡代理,是一種特殊的網絡服務,允許一個網絡終端(一般為客戶端)通過這個服務與另一個網絡終端(一般為服務器)進行非直接的連接。一些網關、路由器等網絡設備具備網絡代理功能。一般認為代理服務有利于保障網絡終端的隱私或安全,防止攻擊。
## 代理分類
代理主要分為兩大類,正向代理和反向代理。
- 正向代理:
通過一個連接了外網的服務器作為中轉站,幫助或代理區域網內的用戶訪問外網。這個連接了外網的服務器叫做正向代理服務器。
- 反向代理:
通過訪問一個內網的**代表**,我們能訪問到這整個內網內的數據。這個代表就稱作反向代理服務器。
### 正向代理和反向代理的區別
正向代理時,用戶在url欄中訪問的是直接目標,并不需要關心代理服務器是什么。
反向代理時,用戶訪問的目標是反向代理服務器,這個反向代理服務器會從內網中拿取數據再傳給用戶,但用戶并不知道**這些數據其實并不是反向代理服務器本身的**。
## 應用
### 設置正向代理
一般我們在公司里需要通過一臺連接到外網的服務器作為代理來訪問外網。怎么給我們自己設置這個代理服務器呢?
一圖頂千言


### 代理
我們通常所說的代理是指正向代理,嗯。。能幫助我們做一些不可描述的事情♂。
一般來說一個代理軟件會改變我們本地的host指向,當我們在url中輸入一個網址(域名)時,先會解析到代理服務器上(這個服務器要和我們的目標網址連的上),然后才是代理服務器幫助我們向用戶的目標地址發起請求,當得到目標地址響應后代理服務器再轉將響應信息傳輸給客戶端。
```
let http = require('http');
let proxy = require('http-proxy');
let proxyServer = proxy.createProxyServer();
let {inspect} = require('util');
// 代理服務器
let server = http.createServer(function(req,res){
proxyServer.web(req,res,{
target:'http://localhost:9999'
})
proxy.on('error', function (err) {
console.log(inspect(err));
res.end('somthing wrong');
});
}).listen(8888)
//--- --- ---
// 目標地址服務器
let http = require('http');
let server = http.createServer(function(req,res){
res.write('9999,');
res.end('9999');
}).listen(9999);
```
#### proxyServer.web()方法實現思路
```
function web(req,res,options){
let {host,port,pathname} = url.parse(req.url);
let opts = {
host
,port
,method:req.method
,path:pathname
,header:req.headers
};
opts.host = options.target;
console.log('-----------')
http.request(opts,function(response){
console.log(response);
response.pipe(res);
});
}
```
### 虛擬機
#### 服務器與虛擬主機
首先我們來看看服務器和虛擬主機的區別
以阿里云等為例,
彈性計算云服務器 ECS:一個完整的服務器
虛擬主機:你得到的只是此服務器上的一個目錄
這里有個問題,一臺ECS上能創建多個虛擬主機(網站),但我們訪問的時候都是通過同一個端口號訪問的,眾所周知,一個端口只能有一個程序,這么多個站點怎么就會不報錯呢?
這就是通過反向代理來實現了。
#### 虛擬機與反向代理
下面我們通過一個簡單的示例來演示反向代理怎么來實現虛擬機的。
為了演示,首先我們在etc下的hosts添加兩個解析

```
www.a.com http://localhost
www.b.com http://localhost
```
可以看到,這兩個解析都指向localhost,同一臺服務器
為了在同一臺服務器上通過訪問不同域名訪問到應該指向的網站,我們需要講域名進行一個映射,**映射到服務器上不同的端口號上**。
>[tip] req.headers['host']長這樣

這樣我們再訪問不同站點時,只需從`req`中拿到的`host`,再根據host查找到映射到的那個端口就可以找到運行在該端口上的站點。
#### 示例源碼
```
let http = require('http');
let proxy = require('http-proxy');
let proxyServer = proxy.createServer();
let config = {
"www.a.com":"http://localhost:8080"
,"www.b.com":"http://localhost:9000"
};
let server = http.createServer(function(req,res){
let host = req.headers['host'];
console.log(host);
let target = config[host];
console.log(target);
if(target){
proxyServer.web(req,res,{
target
})
}else{
res.end(host);
}
}).listen(80);
//--- --- ---
// a.com
let http = require('http');
let server = http.createServer(function(req,res){
res.end('8080');
}).listen(8080);
//b.com
let http = require('http');
let server = http.createServer(function(req,res){
res.end('9000');
}).listen(9000);
```
## 請求報文中的user-agent
此代理非彼代理,這里是指客戶端(瀏覽器和操作系統計算機硬件)的一些信息。我們可通過`req.headers['user-agent']`拿到這些信息,并根據這些信息對我們的應用進行一定的定制優化。
```
let http = require('http');
let userAgentParser = require('user-agent-parser');
let server = http.createServer(function(req,res){
let userAgent = req.headers['user-agent'];
console.log(userAgent);
let userAgentObj = userAgentParser(userAgent);
console.log(userAgentObj);
}).listen(8080);
<<< userAgentObj
{ browser: { name: 'Chrome', version: '64.0.3282.186', major: '64' },
engine: { name: 'WebKit', version: '537.36' },
os: { name: 'Windows', version: '7' },
device: { model: undefined, vendor: undefined, type: undefined },
cpu: { architecture: 'amd64' } }
```