Protocol Relative URLs or Protocol-less URLs(协议相关URL/无协议头的URL)

1
<img src="//domain.com/img/logo.png">

也许这样的一个URL第一眼看去我们会无节操的骂去,谁***把协议头都忘了写了!! 当爆出粗口的刹那间,“年少无知”也许就成了最优雅的形容词。

当再看到这段

1
2
3
4
5
 @font-face {
   font-family: 'Roboto';
   font-style: italic;
   src: url('//themes.googleusercontent.com/font?kit=biUEjW7P-lfzIZFXrcy-wQ') format('woff');
 }

的时候,也许已经开始胆怯了。。。

没错,这也许就是一种你不曾知道的URL。RFC 3986 Protocol relative URLs 是指如果浏览器中请求页面的的协议是HTTPS,那么对于像 “//domain.com/img/logo.png” 这样的URL将同样用HTTPS来请求资源,同理如果是HTTP的话,这个URL将会变为”http://domain.com/img/logo.png“。那么对于这样的规范,有什么用处呢?下面我举一个栗子:

你有一个仅支持http请求的应用,突然有一天需要同时支持http和https, 当你想当然的在web服务器中打开ssl端口的时候,一请求页面发现样式乱了,顿时就慌了。才意识到浏览器中诸如:

1
2
The page at ‘https://a.example.com/1' was loaded over HTTPS, but displayed insecure content from
'http://staic.style.com/static/img/logo.png': this content should also be loaded over HTTPS.

这样的警告。

一个支持https请求的网站,应确保所有的内容都是用https加载的,从而防止如上的mixed content warnings。 当赶紧去把代码中数之不尽hard code的http改为https的时候,可能顿时就一边改一边还窃喜自己顿时就能解决这个问题的时候, 不知又悄悄的引入了一些“不便之处”:当页面是被标准的http请求时,这些被改成https协议的资源,不得不在此时依然通过ssl去连接请求https外部资源。

同时支持http和https,怎样让自己的代码更自然简洁一些,当然是使用 Protocol relative URL。

很简单,不用去管协议,就像这样

1
//some-domain.com/path/to/resource

好了,不再有mixed content警告,可以确定任何放在src和href属性中的url都会被浏览器可靠的处理。同时免去了你使用 (request.protocol / request.scheme 或者 window.location.protocol) 去解析请求的协议。

此时,有没有顿然暗爽~

但是,要注意的是:

  1. 例如存放 js 和 css 文件这样的静态服务器不同时支持https或者是http, 会有潜在的风险~ 比如google analytics曾经就对http和https分别用不同的subdomain来对待, 一个为 http://www.google-analytics.com/ga.js, 一个为https://ssl.google-analytics.com/ga.js
  2. 如果你打算支持IE6的话,它会在IE6下不知所措。
  3. 还有倘若留意一下上面的写法在IE8下面请求一个样式文件的表现的话,你可能会更失望,请求了两次!!(一次http,一次https)不过你决定抛弃IE8的话,那就忽略这条。详细请参考http://www.stevesouders.com/blog/2010/02/10/5a-missing-schema-double-download/
  4. http请求和https请求可能会留有两份不同的cache。
  5. 一些邮件客户端(特别是outlook)将不会以为它是http或者https协议,而是会以为是本机上的资源从而使用file://协议

倘若这些都不是问题,那就尝试一下Protocol relative URL

跨源资源共享 Cross Origin Resource Sharing(CORS)

什么是跨域?

JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。通常来说,跨域分为以下几类:

URL 说明 是否允许
http://www.a.com/a.js http://www.a.com/b.js 同一域名下 允许
http://www.a.com/lab/a.js http://www.a.com/script/b.js 同一域名下不同文件夹 允许
http://www.a.com:8000/a.js http://www.a.com/b.js 同一域名,不同端口 不允许
http://www.a.com/a.js https://www.a.com/b.js 同一域名,不同协议 不允许
http://www.a.com/a.js http://70.32.92.74/b.js 域名和域名对应ip 不允许
http://www.a.com/a.js http://script.a.com/b.js 主域相同,子域不同 不允许
http://www.a.com/a.js http://a.com/b.js 同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js http://www.a.com/b.js 不同域名 不允许

在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。

CORS

通过XHR实现Ajax通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR对象只能访问与包含它的页面位于同一个域中的资源。但是合理的跨域请求对开发某些浏览器应用程序至关重要。 CORS的背后基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求响应是应该成功还是应该失败。

比如:一个请求附加了一个额外的Origin头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。

1
Origin: http://www.example.com

如果服务器认为这个请求可以接受,就在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共资源可以回发“*”),例如:

1
Access-Control-Allow-Origin: http://www.example.com

如果是没有这个头部,或者有这个头部但是源信息不匹配,浏览器就会驳回请求。

1. 处理一个简单请求

1
2
3
  var url = 'http://api.bob.com/cors';
  var xhr = createCORSRequest('GET', url);
  xhr.send();

这段javascirpt代码会发送一个GET请求,会伴随一个真实的浏览器HTTP请求被发出

1
2
3
4
5
6
7
HTTP request:
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0

CORS请求总是会包含由浏览器添加的“origin” header,它的值由scheme(e.g. http/https), domain(e.g. bob.com), 和端口号(只被包含如果非default 80端口 e.g.81)组成。例如:www.bob.com 但是origin header的存在与否并不能说明它是不是一个跨域请求,因为有一些同域的请求也会包含这个header。比如,在firefox上同域请求是不包含origin的,但是在chrome和safari中put/post/delete同域请求包含origin header。浏览器并不期望同域请求的response里包含CORS response header。

所有和CORS相关的response header都是以“Access-Control-“为前缀的:

  • Access-Control-Allow-Origin(必须) 这个必须包含在所有合法的跨域请求的response中,其值要么是Origin header中的值,要么就是”*“允许任何域的请求。
  • Access-Control-Allow-Credentials(可选),默认情况下cookie是不包含在CORS请求中的,使用这个header将会指明要在CORS请求中包含cookie,它的有效值是true, 如果不需要cookie, 正确的做法不是将其值设为false, 而是根本就不要这个包含header.
  • Access-Control-Expose-Header(可选),XMLHttpRequest 2 object中有一个getResponseHeader()方法,用以返回特定的response header,但是它只能得到简单的响应header,如果想让客户端访问到其他的一些header, 必须设定这个 Access-Control-Expose-Header,它的值是以逗号分隔的你想暴漏给客户端的header。

2. 处理一个不简单的请求

除了GET之外,如果想使得请求具备譬如HTTP PUT/DELETE动作,或者支持Content-Type: application/json, 就需要去处理被我们称做是not-so-simple的请求。

1
2
3
4
var url = 'http://api.bob.com';
var xhr = createCORSRequest('PUT', url);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

Preflight Request

1
2
3
4
5
6
7
8
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

CORS允许使用自定义header以及出了GET和POST以外的其他方法,不同body内容类型通过一种透明的服务器验证机制被称做是Preflighted Request,当试图发起真实请求的时候,”preflight“请求就已经先发送给服务器端。这种请求使用OPTIONS方法,同时也通常会附带一些额外的header,

  • Origin
  • Access-Control-Request-Method 请求希望使用的http方法
  • Access-Control-Request-Headers (可选)用逗号分隔的自定义header列表 例如:一个带有自定义header X-Custom-Header的PUT请求: Origin: http://api.bob.com
  • Access-Control-Request-Method: PUT
  • Access-Control-Request-Headers: X-Custom-Header 通过这个请求,服务器可以判断是否支持这种类型请求,服务器通过发送以下header的response进行响应:
1
2
3
4
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 1728000
  • Access-Control-Allow-Origin 同一般请求一样.
  • Access-Control-Allow-Methods 以逗号分隔的允许使用的方法列表
  • Access-Control-Allow-Headers 以逗号分隔的允许使用的header列表
  • Access-Control-Max-Age 以秒为单位的preflight的缓存时间

一旦Preflight Request给予许可,浏览器将会发起真实请求。

1
2
3
4
5
6
7
PUT /cors HTTP/1.1
Origin: http://api.bob.com
User-Agent: Mozilla/5.0
Connection: keep-alive
Accept-Language: en-US
X-Custom-Header: value
Host: api.alice.com

真实的响应:

1
2
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

如果服务器想拒绝一个CORS请求,可以只返回一个不带CORS header的响应(HTTP 200)。服务器可能会拒绝不合法的preflight请求,因为没有专门的CORS header在响应中,浏览器会假定请求是不合法的,所以就不会发真实的请求。 Preflight Request

1
2
3
4
5
6
7
OPTIONS /cors HTTP/1.1
Connection: keep-alive
Accept-Language: en-US
Host: api.alice.com
Access-Control-Request-Headers: X-Custom-Header
Access-Control-Request-Method: PUT
Origin: http://api.bob.com

Preflight Response:

1
2
// ERROR - No CORS headers, this is an invalid request!
Content-Type: text/html; charset=utf-8

备注:$.support.cors将会被设置为true如果浏览器支持CORS。JQuery $.ajax()方法可以用于通常的XHR请求也可以用于CORS请求,但是它的实现当中不支持IE的XDomainRequest对象(IE8),但是有JQuery的插件去补充它[http://bugs.jquery.com/ticket/8283],

我不知道的URL Encode

为什么使用URL encoding?

URL在Internet中传输只能是ASCII字符集,正是由于在URL中往往包含很多非ASCII字符之外的字符,才需要将它转化。通常会用“%”和两个十六进制的数字去替换非法的ASCII字符(percent_encode)。

当输入到HTML form表单中的数据被提交的时候,表单中的名字,值都会被编码通过http请求GET, POST等发送给服务器端,默认的编码方式是基于早期版本的general URI perccent-encoding规则以及一些改进。通过这种形式编码的数据的MIME格式是 application/x-www-form-urlencoded。当发送的是HTTP GET请求的时候,application/x-www-form-urlencoded包含在请求URI的query部分。当发送的是HTTP POST请求时,数据本身被放在信息的body中,media-type放在信息的Content-Type header中。

General URL Sytanx

对于URL: https://www.example.com:8080/file;p=1?q=2#third

Part Data
Scheme https
Host www.example.com
Port 8080
Path file
Path parameters p=1
Query parameters q=2
Fragment third

URL grammar

对于URL是如何组装和如何分割,是有着一定的语法。比如,”://“ 是分割scheme和host, ”/“是分割host和path fragment, query通常跟着”?“之后。这就意味着特定的某些字符会因为语法的需求而被保留。有些是针对所有的URI都保留,有些则是针对某些scheme。所有这些被保留的字符是如果是出现在不被允许使用的地方就需要URL encoded。举个例子”?“如果出现在path fragment中就需要被转换成”%3F“, 从而使它不具备了句法含义(syntactic meaning)。”http://www.example.com/to_be_or_not_to_be?.jpg“转换成”http://www.example.com/to_be_or_not_to_be%3F.jpg”, 而不会被误认为”?“在这里是一个query部分。

The unreserved characters can be encoded, but should not be encoded. The unreserved characters are:

1
2
3
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9 - _ . ~

The reserved characters have to be encoded only under certain circumstances. The reserved characters are:

1
! * ' ( ) ; : @ & = + $ , / ? % # [ ]

Pitfalls

  • 保留字符并不是你想象中的那样被编码

    • “?”若在query部分可以不被转义

    • “/” 若在query部分可以不被转义

    • “=” 若在path parameter或query parameter部分或者是path segment中可以不被转义

    • “:@-._~!$&‘()*+,;=” 若在path segment中将可以不被转义

    • “/?:@-._~!$&‘()*+,;=” 若在fragment部分将可以不被转义

  • 保留字符在URL不同的部分会被不同的对待

    “ ”(空格)在path fragment部分被编码成”%20”(绝对不是”+“),而”+“会被保留, 不会被编码。而” “(空格)在query部分可能会被编码成”+“(为了向后兼容)或者是”%20”, 然而”+“则会被转义成”%2B”。、


实践小结:在ruby中我们经常使用的URL encode的方法大致有三种

  • URI.escape()是替换掉不安全的字符,但是转义出来的URL在query部分含有”+“并不能和上述标准十分相符,而URI.encode()是URI::Escape#escape的别名
  • CGI.escape()本义是对HTML form表单请求做转义,它的转义比较彻底,看起来也比较残暴(但是也相当好用),最起码能保证编码出来的URL肯定是合法的
  • 当和URIs打交道, 也可以使用Addressable, 它提供了url encoding, form encoding 和 normalizes URLs.
1
2
URI.encode('www.example.com/François+?a=3+4')
=> "www.example.com/Fran%C3%A7ois+?a=3+4”

“+”该被编码的

1
2
CGI.escape("www.example.com/François+?a=3+4”)
=> "www.hel.com%2FFran%C3%A7ois%2B%3Fa%3D3%2B4

看起来几乎所有的保留字符都被编码了

引用:【http://blog.lunatech.com/2009/02/03/what-every-web-developer-must-know-about-url-encoding