标签归档:HTTP请求

Cross-Origin Resource Sharing

受浏览器的同源策略限制,JavaSript只能请求本域内的资源。跨域资源共享(Cross-Origin Resource Sharing, CORS)是为解决Ajax技术难实现跨域问题而提出的一个规范,这个规范试着从根本上解决安全的跨域资源共享问题。在此之前,解决此类问题的途径往往是服务器代理、JSONP等,治标不治本。目前基本所有浏览器都已经支持该规范。

一个域是由schema、host、port三者共同组成,与路径无关。所谓跨域,是指在http://example-foo.com/域上通过XMLHttpRequest对象调用http://example-bar.com/域上的资源。CORS约定服务器端和浏览器在HTTP协议之上,通过一些额外HTTP头部信息,进行跨域资源共享的协商。服务器端和浏览器都必需遵循规范中的要求。

CORS把HTTP请求分成两类,不同类别按不同的策略进行跨域资源共享协商。

1. 简单跨域请求。
当HTTP请求出现以下两种情况时,浏览器认为是简单跨域请求:

1). 请求方法是GET、HEAD或者POST,并且当请求方法是POST时,Content-Type必须是application/x-www-form-urlencoded, multipart/form-data或着text/plain中的一个值。
2). 请求中没有自定义HTTP头部。

对于简单跨域请求,浏览器要做的就是在HTTP请求中添加Origin Header,将JavaScript脚本所在域填充进去,向其他域的服务器请求资源。服务器端收到一个简单跨域请求后,根据资源权限配置,在响应头中添加Access-Control-Allow-Origin Header。浏览器收到响应后,查看Access-Control-Allow-Origin Header,如果当前域已经得到授权,则将结果返回给JavaScript。否则浏览器忽略此次响应。

2. 带预检(Preflighted)的跨域请求。
当HTTP请求出现以下两种情况时,浏览器认为是带预检(Preflighted)的跨域请求:

1). 除GET、HEAD和POST(only with application/x-www-form-urlencoded, multipart/form-data, text/plain Content-Type)以外的其他HTTP方法。
2). 请求中出现自定义HTTP头部。

带预检(Preflighted)的跨域请求需要浏览器在发送真实HTTP请求之前先发送一个OPTIONS的预检请求,检测服务器端是否支持真实请求进行跨域资源访问,真实请求的信息在OPTIONS请求中通过Access-Control-Request-Method Header和Access-Control-Request-Headers Header描述,此外与简单跨域请求一样,浏览器也会添加Origin Header。服务器端接到预检请求后,根据资源权限配置,在响应头中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分别表示允许跨域资源请求的域、请求方法和请求头。此外,服务器端还可以加入Access-Control-Max-Age Header,允许浏览器在指定时间内,无需再发送预检请求进行协商,直接用本次协商结果即可。浏览器根据OPTIONS请求返回的结果来决定是否继续发送真实的请求进行跨域资源访问。这个过程对真实请求的调用者来说是透明的。

XMLHttpRequest支持通过withCredentials属性实现在跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息)。浏览器将携带Cookie Header的请求发送到服务器端后,如果服务器没有响应Access-Control-Allow-Credentials Header,那么浏览器会忽略掉这次响应。

这里讨论的HTTP请求是指由Ajax XMLHttpRequest对象发起的,所有的CORS HTTP请求头都可由浏览器填充,无需在XMLHttpRequest对象中设置。以下是CORS协议规定的HTTP头,用来进行浏览器发起跨域资源请求时进行协商:
1. Origin。HTTP请求头,任何涉及CORS的请求都必需携带。
2. Access-Control-Request-Method。HTTP请求头,在带预检(Preflighted)的跨域请求中用来表示真实请求的方法。
3. Access-Control-Request-Headers。HTTP请求头,在带预检(Preflighted)的跨域请求中用来表示真实请求的自定义Header列表。
4. Access-Control-Allow-Origin。HTTP响应头,指定服务器端允许进行跨域资源访问的来源域。可以用通配符*表示允许任何域的JavaScript访问资源,但是在响应一个携带身份信息(Credential)的HTTP请求时,Access-Control-Allow-Origin必需指定具体的域,不能用通配符。
5. Access-Control-Allow-Methods。HTTP响应头,指定服务器允许进行跨域资源访问的请求方法列表,一般用在响应预检请求上。
6. Access-Control-Allow-Headers。HTTP响应头,指定服务器允许进行跨域资源访问的请求头列表,一般用在响应预检请求上。
7. Access-Control-Max-Age。HTTP响应头,用在响应预检请求上,表示本次预检响应的有效时间。在此时间内,浏览器都可以根据此次协商结果决定是否有必要直接发送真实请求,而无需再次发送预检请求。
8. Access-Control-Allow-Credentials。HTTP响应头,凡是浏览器请求中携带了身份信息,而响应头中没有返回Access-Control-Allow-Credentials: true的,浏览器都会忽略此次响应。

Reference:
[1] HTTP access control (CORS). https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS.
[2] Server-Side Access Control. https://developer.mozilla.org/en-US/docs/Server-Side_Access_Control.
[3] CORS In Action. http://arunranga.com/examples/access-control/.

--EOF--

Reverse Ajax分类

Ajax默认只能从客户端向服务器端发出请求,服务器端进行响应。Reverse Ajax(反向Ajax)是这样概念:可以从服务器端推送数据到客户端。

实现Reverse Ajax有以下几种技术:

 一. 轮询Polling

轮询方式是指普通Ajax请求以固定时间间隔向服务器端发出请求,服务器的事件只有在客户端下次请求时才返回。固定时间间隔越大,服务器资源(带宽、连接数等)消耗越低,但是通信延时高。固定时间间隔越小,服务器资源消耗大,但是通信延时小。轮询不具备伸缩性。

 二. Piggyback捎带轮询

Piggyback方式允许客户端在需要的时候向服务器端发送请求,如果服务器端有更新内容要发送到客户端,它会等待客户端下一次请求,然后把期间所有已经就绪的数据一起返回。Piggyback不具备伸缩性。

 三. Comet

Comet 是一个 Web 应用模型,在该模型中,请求被发送到服务器端并保持一个很长的生存期,直到超时或是有服务器端事件发生,在该请求完成后,发出另一个长生存期的 Ajax 请求去等待另一个服务器端事件。

Comet可分为HTTP流模式和长轮询(long polling)模式两类。
1. HTTP流模式:
    1). 页面中放置一个的隐藏Iframe标签,该标签的src属性指向返回服务器端事件的 servlet 路径。每次在事件到达时,servlet 写入并刷新一个新的 script 标签,该标签内部带有 JavaScript 代码,iframe 的内容被附加上这一 script 标签,标签中的内容就会得到执行。
    2). Servlet 3.0 + ajax multi-part可以实现一个长连接接收多次响应(也可理解为一个响应分多次返回)。一些浏览器支持XMLHttpRequest对象的multi-part标记置为ture,在服务器端需要异步Servlet支持(Servlet 3.0)。缺点:不是所有浏览器都支持multi-part标记。只有Gecko内核浏览器才支持,如Firefox。

2.长轮询(long polling)模式:
一个长连接由服务器端保持着打开的状态,只要一有事件发生,响应就会被提交,然后连接关闭。接下来一个新的长连接会被客户端重新打开,然后重复上述过程。
    1). 利用iframe标签。服务器端挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个 script 标签来获取下一个事件。
    2). XMLHttpRequest长轮询:客户端打开一个到服务器端的Ajax请求然后等待响应。服务器端挂起请求,只要一有事件发生,服务器端就会在挂起的请求中返回响应并关闭该请求,以及关闭servlet响应的输出流。然后客户端打开一个新的到服务器端的长生存期的Ajax请求。Ajax长轮询请求Comet能在所有支持简单Ajax请求的浏览器上正常工作。

 四. Websocket

WebSocket支持双向、全双工通信信道,通过HTTP请求(也称为 WebSocket握手)和一些特殊的标头 (Header)使连接一直处于激活状态。客户端可以用JavaScript发送和接收数据,就像使用TCP socket一样,可由浏览器商本地(native)实现 ,或通过将调用委托给称为FlashSockets的隐藏的Flash组件的网桥来实现。服务器端对WebSocket的处理情况复杂些,现在还没有Java规范以标准的方式来支持WebSocket。要使用Web容器(例如Tomcat或Jetty)的WebSocket功能就要把应用代码和容器特定的库紧密耦合。jWebSockets是采用 Java 和 JavaScript 开发的实现了 HTML5 Websocket 协议的开源框架, 包含了jWebSocket Server、jWebSocket Clients和jWebSocket FlashBridge。

目前已经支持WebSocket的浏览器有IE 10+, Firefox 11+, Chrome 14+, Safari 6+, Opera 12.1+, iOS Safari 6.0+, Blackberry Browser 7+, Opera Mobile 12.1+, Chrome for Android 25+, Firefox for Android 19+.

支持Reverse Ajax 的类库最佳选择将是能检测到是否支持WebSocket,如果不支持WebSocket,则回退到Comet(长轮询)。

Reference:
[1] IBM developerWorks, 反向 Ajax,第 1 部分: Comet 简介.
[2] IBM developerWorks, 反向 Ajax,第 2 部分: WebSockets.
[3] jWebSocket, http://jwebsocket.org.

--EOF--

curl发送http请求

curl可以通过命令行或脚本的方式调用http接口,特别在调用rest接口时非常有用。

最简单的GET一个页面可以直接输入curl url,例如:curl www.fengchj.com。

通过几个参数更方便看清http的请求-响应过程。

-X METHOD: -X参数用来指定http method,默认为GET,可以无需指定。例如 -X PUT, -X POST -X DELETE等等。
-H HEADER: -H参数用来指定http header,例如设置Content-type可以通过-H "Content-type: Application/json"实现,设置自定义头X-header可以通过-H "X-header: my-custom-header"实现。
-d CONTENT: 如果请求的方法是POST或者PUT,一般情况下该请求都会携带请求体内容, 可用-d参数用来指定http body。
-i: 参数-i用来显示http响应的header。

举个栗子:

1
2
curl -i -X POST -H “Content-type: application/json" -H "Accept: application/json" 
-d "{\"name\": \"kaka\", \"pass\":\"123\"}" www.fengchj.com/login

上面这个curl命令表示向www.fengchj.com/login这个地址POST一个http请求,请求体内容为{"name": "kaka", "pass":"123"},请求头有Content-type和Accept,通知服务器端以json方式解析请求体,并表示接受json的响应体格式。-i会显示响应头。

============2013-03-26================

补充参数:
--cookie: 附带cookie。如:--cookie "cookieName1=cookieValue1;cookieName2=Value2"
-v: verbose,详细HTTP请求。包含Response Code, Request Header和Response Header等。

--EOF--

短网址API调用实例(PHP实现)

短网址是随着微博的流行而出现,目前市面上的短网址服务林林总总不计其数,评价一个短网址服务好坏的最重要标准是其服务的稳定性。试想,短网址服务做的工作就是为原先的长网址加一层映射关系,而这种映射关系是握在短网址服务提供商手上的,如果这个环节出了问题,导致映射关系丢失,那也就意味着再也无法找回原先的长网址。

以上差不多都是废话。

国内外比较有实力的短网址提供商如goo.gl(Google)、dwz.cn(百度)、is.gd等都提供了API的方式供外部生成短网址,该方式只需模拟一个HTTP请求,以长网址作为参数向服务器发送该请求,如果请求成功,服务器就会返回缩短以后的短网址。

有些API要求将HTTP请求以POST方式提交,例如goo.gl和dwz.cn(demo),这时可以采用基于PHP的cURL库来完成HTTP请求的发送和接收。以goo.gl为例(更多细节可参考『Google URL Shortener API』):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$apiKey = 'APIKEY'; //google短网址所需。
//API key从http://code.google.com/apis/console/得到
$postData = array('longUrl' => "http://fengchj.com", 'key' => $apiKey);
$jsonData = json_encode($postData);
$co = curl_init();
curl_setopt($co, CURLOPT_URL, 'https://www.googleapis.com/urlshortener/v1/url');
curl_setopt($co, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($co, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($co, CURLOPT_HEADER, 0);
curl_setopt($co, CURLOPT_HTTPHEADER, array('Content-type:application/json'));
curl_setopt($co, CURLOPT_POST, 1);
curl_setopt($co, CURLOPT_POSTFIELDS, $jsonData);
$strResponse = curl_exec($co);
curl_close($co);
$arrResponse = json_decode($strResponse );
print $arrResponse->id; //得到http://goo.gl/xxxx类型的短网址。

有些短网址服务API接受以GET方式提交的长网址,例如is.gd,用户只需将长网址作为参数向提供商给出的URL发送请求即可,如:

1
print file_get_contents("http://is.gd/create.php?format=simple&url=http://fengchj.com");

实际上这里也可采用cURL库进行GET方式HTTP请求的封装,但是由于GET方式的幂等特性,用file_get_contents来请求给定URL页面更为方便。相对于cURL库,file_get_contents方式的缺点在于无法对HTTP请求过程进行错误处理。

调用外部API生成短网址的效果可参见http://fengchj.com/?page_id=1619

--EOF--

为Subversion添加HTTP支持

小团队使用SVN时用轻量级的svnserve服务器程序就足够了,又灵巧又方便配置,因为服务器端和客户端使用的协议是专为SVN设计的,所以效率也比其他类型SVN服务器(如Apache的httpd)要高些。相比之下,把Apache配置成一个SVN服务器就显得有些“笨重”了,不过它也有svnserve无法比拟的优势,比如它可以为SVN提供完善的日志记录功能和内置Web方式访问版本库,并且添加HTTP支持可以使得版本库在广域网范围内都可以方便地使用和管理。

本文的实验环境在Windows下,不过以下这些配置虽然平台相关,但是稍微转换一下就能在Linux上生效,并没有什么鸿沟在里边。

为SVN服务器添加HTTP支持主要有以下几个步骤:
1、首先安装Apache和Subversion,我这里用的版本是httpd-2.2.19-win32-x86-no_ssl.msi和Setup-Subversion-1.6.5.msi。SVN安装后,在命令行下敲svn命令,如果不提示“'svn'不是内部或外部命令,也不是可运行的程序或批处理文件。”的表示安装成功了。

2、使用svnadmin命令为项目新建一个库,这个库就是用来存储项目代码的版本控制信息的。比如E盘下有目录E:\svnroot\proj,要把该目录设置为proj项目的库,可以用以下命令:

1
svnadmin create E:\svnroot\proj

如果命令运行成功,proj目录下会生成多个目录和文件,比较重要的是conf目录。如果采用svnserve作为svn服务器,那么所有认证和权限控制的配置都在这个目录中进行,比如配置svnserve.conf、passwd和authz文件。因为我们采用的是Apache服务器,所以这些文件的配置可以省去。

3、集成Apache,添加HTTP支持。SVN已经为集成做了准备,在SVN的安装目录%SUBVERSION_HOME%/bin目录下有两个module:mod_dav_svn.so和mod_authz_svn.so。Apache服务器就是通过这两个module,将接收到的HTTP请求通过dav_module代理给SVN服务器,以此来实现以HTTP方式访问svn版本库。所以为了配置方便,最好将bin目录下的两个模块mod_dav_svn.so和mod_authz_svn.so拷贝到%APACHE_HOME%/modules目录下。

4、配置Apache的httpd.conf文件,引入所需模块。在httpd.conf的LoadModule区域的末尾添加:

1
2
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so

这里有一个依赖性的问题,因为mod_dav_svn.so模块依赖于mod_dav.so模块,而默认情况下,Apache服务器并不加载mod_dav.so模块,所以在此之前要把#LoadModule dav_module modules/mod_dav.so前的#号注释去掉。

5、配置URL与SVN版本库之间的映射关系。还是在httpd.conf文件中,使用Apache的<Location>配置段可以配置(关于<Location>配置段的说明可以参见文章『Apache核心(Core)指令<Location>和<Directory>区别』)。具体配置见下:

1
2
3
4
5
6
7
8
9
<Location /projname>
    DAV svn
    SVNPath E:/svnroot/proj
 
    AuthType Basic
    AuthName "Subversion repositories"
    AuthUserFile  E:/svnroot/proj/conf/users
    Require valid-user
</Location>

该配置表示作用于所有URL中以/projname开头的请求。当Apache收到这样的请求时,就会去SVNPath指定的目录下去请求资源(通过module_dav代理)。AuthType表示认证类型为Basic,即用户名/密码。AuthUserFile用于指定用户信息文件的所在,该文件在下一步会产生。Require valid-user表示不允许匿名请求。

6、生成认证文件。Apache服务器提供了认证管理的工具htpasswd.exe,位于%APACHE_HOME%bin目录下,该命令的用法可以用/?选项查看其帮助信息。假如现在要生成user1和user2两个用户,密码分别为passwd1和passwd2,可以用以下命令生成用户认证文件:

1
2
3
4
C:\Apache2.2\bin>htpasswd.exe -cb users user1 passwd1
Adding password for user user1
C:\Apache2.2\bin>htpasswd.exe -b users user2 passwd2
Adding password for user user2

因为htpasswd命令的选项-c表示重新生成文件,所以第二条命令就不用该参数了。命令执行成功后,把bin目录下的users文件拷贝到httpd.conf文件中配置的AuthUserFile指定的位置去。

7、重启Apache服务器。因为修改过httpd.conf文件,所以最后一步是重启Apache服务器,使配置生效。 这时在浏览器中输入http://localhost/projname,如果出现要求输入用户名和密码的对话框,则表示带HTTP支持的SVN服务器配置成功。

Tips:
1、要想以更细的粒度为每个用户分配权限,可以在<Location>配置段中使用AuthzSVNAccessFile命令,详情可参考『使用Subversion进行版本控制』中的『每目录访问控制』章节
2、新安装的Apache服务器不会随开机启动,可以将其注册为系统服务。注册为系统服务还有一个好处是服务器程序不会显式地显示在任务栏中。注册为服务的命令为:

1
C:\Apache2.2\bin>httpd.exe -k install -n "Apache2.2 "

再去计算机的服务管理页面就可以配置Apache服务是否需自动启动。
3、Apache和Subversion某些版本的组合可能会出现类似如下的错误,具体表现为Apache服务器无法启动:

1
2
httpd.exe: Syntax error on line 129 of C:/Apache2.2/conf/httpd.conf: 
Cannot load C:/Apache2.2/modules/mod_dav_svn.so into server: \xd5\xd2\xb2\……

这个问题一般出现在Apache中引入mod_dav_svn.so模块时,猜测原因可能是mod_dav_svn.so本身是共享对象(Shared Object),它的实现也有依赖于其他模块,而Apache服务器中无法找到这些依赖模块,这些模块对Apache不可见。解决方法是把%SUBVERSION_HOME%/bin目录下的所有*.dll文件拷贝到%APACHE_HOME%bin目录中(提示已存在的不必进行覆盖)。
4、实际上有VisualSVN Server这样的工具,集成Apache和SVN,所有配置均是图形界面完成,界面非常友好。用本文的方法为SVN添加HTTP支持只是满足一下折腾的欲望。:-)

--EOF--