什么是代理?代理是作为中间层,作用于上下游两端。将下游的“请求”转移提交到上游,将上游的“响应”提交给下游。代理不仅在设计模式等程序设计中有比较广泛的使用,同时在大型的系统工程中使用也比较广泛,在web服务器领域,代理出现的频率也是很高的。
web服务中的代理有正向代理和反向代理两种,在大型的web服务中,反向代理是一种非常常用的技术,使用反向代理技能起到转发请求的作用,同时也能做到请求的控制及均衡。
正向代理主要的机制是 客户端预先能知道代理服务器的地址及端口,通过客户端的设置来访问代理服务器,继而代理服务器产生作用以完成请求的响应处理,常用的ss vpn就是使用正向代理的方式来做到访问外部网站的,简单地理解,正向代理的代理服务器是由客户端"发现"的。
反向代理的机制和正向代理在取得代理服务器地址端口的方式是不一样的,反向代理不需要客户端了解代理服务器,无需感知代理服务器的任何信息,通过访问域名通过解析服务得到代理服务器的地址和端口,从而完成访问,反向代理常见的一种功能是cdn(内容分发网络)中的缓存服务的关键功能。
1.下图是正向代理的图解
2.反向代理图解
通过图解应该能容易看出正向代理和反向代理的区别。
以nginx的proxy模块为例来介绍web代理服务的实现的细节。nginx的proxy模块提供了代理的头设置及对后端响应进行解析处理的功能,实际上proxy模块基本也是做了设置和解析处理的功能,具体的上游服务器的连接 接收及到向下游请求端的转发及web内容的缓存主要是nginx upjistream模块来提供的,nginx upstream实现的细节的介绍可以在点击打开链接这个连接中看到。
nginx proxy模块提供了比较多的配置及设置功能,包括变量设置后端的server路径及协议、连接及响应的超时设置、buffer缓冲的设置、向上游获取数据的限速设置、缓存的控制等。会有一篇blog专门用来介绍nginx 代理功能的配置及细节的要点。本篇blog主要详细说明proxy模块的实现。
typedef struct {
ngx_array_t caches; /*http缓存数组 意味着可以配置多个缓存目录*/
} ngx_http_proxy_main_conf_t
struct ngx_http_proxy_rewrite_s { /*代理url重定向处理*/
ngx_http_proxy_rewrite_pt handler; /*重定向处理函数指针*/
union {
ngx_http_complex_value_t complex; /*规则变量*/
#if (NGX_PCRE)
ngx_http_regex_t *regex;
#endif
} pattern;
ngx_http_complex_value_t replacement; /*url内容替换变量*/
}
typedef struct { /*代理使用的http变量*/
ngx_str_t key_start; /**/
ngx_str_t schema; /*协议名*/
ngx_str_t host_header; /*host头*/
ngx_str_t port; /*端口*/
ngx_str_t uri; /*uri*/
} ngx_http_proxy_vars_t
typedef struct { /*代理头结构 可以有多个http头*/
ngx_array_t *flushes;
ngx_array_t *lengths;
ngx_array_t *values;
ngx_hash_t hash;
} ngx_http_proxy_headers_t
typedef struct { /*代理location域配置*/
ngx_http_upstream_conf_t upstream; /*upstream配置*/
ngx_array_t *body_flushes; /*代理包体数据 用于发送到上游服务器*/
ngx_array_t *body_lengths;
ngx_array_t *body_values;
ngx_str_t body_source;
ngx_http_proxy_headers_t headers; /*上面的代理头*/
#if (NGX_HTTP_CACHE)
ngx_http_proxy_headers_t headers_cache; /*包含了缓存控制的代理头*/
#endif
ngx_array_t *headers_source; /*原始头*/
ngx_array_t *proxy_lengths; /*保存了脚本引擎信息 配合proxy_values使用*/
ngx_array_t *proxy_values;
ngx_array_t *redirects; /*url重定向处理数组*/
ngx_array_t *cookie_domains; /*cookie信息配置*/
ngx_array_t *cookie_paths;
ngx_http_complex_value_t *method; /*访问的method变量 如GET HEAD POST...*/
ngx_str_t location; /*重定向的location*/
ngx_str_t url; /*重定向的url*/
#if (NGX_HTTP_CACHE)
ngx_http_complex_value_t cache_key; /*配置的缓存key值变量*/
#endif
ngx_http_proxy_vars_t vars; /*代理所用的变量*/
ngx_flag_t redirect; /*是否重定向*/
ngx_uint_t http_version; /*http协议版本*/
ngx_uint_t headers_hash_max_size; /*hash表最大值*/
ngx_uint_t headers_hash_bucket_size; /*hash表表元素数量*/
#if (NGX_HTTP_SSL)
ngx_uint_t ssl; /*https代理访问*/
ngx_uint_t ssl_protocols;
ngx_str_t ssl_ciphers;
ngx_uint_t ssl_verify_depth;
ngx_str_t ssl_trusted_certificate;
ngx_str_t ssl_crl;
ngx_str_t ssl_certificate;
ngx_str_t ssl_certificate_key;
ngx_array_t *ssl_passwords;
#endif
} ngx_http_proxy_loc_conf_t
typedef struct { /*代理处理的上下文结构*/
ngx_http_status_t status; /*http状态*/
ngx_http_chunked_t chunked; /*chunked传输信息*/
ngx_http_proxy_vars_t vars; /*变量*/
off_t internal_body_length; /*响应包体长度信息*/
ngx_chain_t *free; /*处理chain */
ngx_chain_t *busy;
unsigned head:1; /*HEAD请求*/
unsigned internal_chunked:1; /*包体是chunked方式传输*/
unsigned header_sent:1; /*响应头数据是否已经发送到请求端*/
} ngx_http_proxy_ctx_t
nginx proxy模块处理流程
1.配置content_handler处理函数 由proxy_pass设置来完成
static char *
ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
...
if (plcf->upstream.upstream || plcf->proxy_lengths) { /*已经配置过了*/
return "is duplicate";
}
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_proxy_handler; /*设置content_handler为proxy_handler处理*/
if (clcf->name.data[clcf->name.len - 1] == '/') { /*路径配置以"/"结束 自动跳转*/
clcf->auto_redirect = 1;
}
value = cf->args->elts;
url = &value[1]; /*取得配置的proxy_pass配置的路径*/
n = ngx_http_script_variables_count(url); /*解析配置的路径 得到变量的数量*/
if (n) { /*代理的路径存在变量*/
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
sc.cf = cf;
sc.source = url;
sc.lengths = &plcf->proxy_lengths;
sc.values = &plcf->proxy_values;
sc.variables = n;
sc.complete_lengths = 1;
sc.complete_values = 1;
if (ngx_http_script_compile(&sc) != NGX_OK) { /*通过脚本引擎 编译变量*/
return NGX_CONF_ERROR;
}
#if (NGX_HTTP_SSL)
plcf->ssl = 1; /*安装了ssl功能 表示ssl可用*/
#endif
return NGX_CONF_OK;
}
if (ngx_strncasecmp(url->data, (u_char *) "http://", 7) == 0) { /*配置的路径以http起头 自动设置端口*/
add = 7;
port = 80;
} else if (ngx_strncasecmp(url->data, (u_char *) "https://", 8) == 0) {/*同上*/
#if (NGX_HTTP_SSL)
plcf->ssl = 1;
add = 8;
port = 443;
#else
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"https protocol requires SSL support");
return NGX_CONF_ERROR;
#endif
} else { /*没有配置协议(http或者https)*/
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid URL prefix");
return NGX_CONF_ERROR;
}
ngx_memzero(&u, sizeof(ngx_url_t));
/*设置upstream的url及端口 no_resolve为真 还未开始解析域名*/
u.url.len = url->len - add;
u.url.data = url->data + add;
u.default_port = port;
u.uri_part = 1;
u.no_resolve = 1;
/*增加一个upstream */
plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
if (plcf->upstream.upstream == NULL) {
return NGX_CONF_ERROR;
}
/*设置proxy location域的schema变量*/
plcf->vars.schema.len = add;
plcf->vars.schema.data = url->data;
plcf->vars.key_start = plcf->vars.schema;
/*将upstream的host及端口设置到变量中*/
ngx_http_proxy_set_vars(&u, &plcf->vars);
/*location是当前location域的名称*/
plcf->location = clcf->name;
if (clcf->named
#if (NGX_PCRE)
|| clcf->regex
#endif
|| clcf->noname)
{ //
if (plcf->vars.uri.len) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"proxy_pass\" cannot ha