tag:blogger.com,1999:blog-59465307047421309702024-03-06T16:20:07.273+08:00Jax 的工作紀錄除了在整理學習上的經驗,同時也能幫助其他需要的人Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.comBlogger4125tag:blogger.com,1999:blog-5946530704742130970.post-55213134146871729112022-07-15T11:21:00.004+08:002023-02-25T18:11:31.288+08:00[轉載] 跨域资源共享 CORS 详解轉載自:<a href="http://www.ruanyifeng.com/blog/2016/04/cors.html" target="_blank">阮一峰 跨域资源共享 CORS 详解</a>
<p>CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。</p>
<p>它允许浏览器向跨源服务器,发出<a href="https://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html" target="_blank"><code>XMLHttpRequest</code></a>请求,从而克服了AJAX只能<a href="https://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html" target="_blank">同源</a>使用的限制。</p>
<p>本文详细介绍CORS的内部机制。</p>
<h3>一、简介</h3>
<p>CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。</p>
<p>整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。</p>
<p>因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。</p>
<h3>二、两种请求</h3>
<p>浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。</p>
<p>只要同时满足以下两大条件,就属于简单请求。</p>
<dl>
<dt>(1) 请求方法是以下三种方法之一:</dt>
<dd>HEAD</dd>
<dd>GET</dd>
<dd>POST</dd>
<dt>(2)HTTP的头信息不超出以下几种字段:</dt>
<dd>Accept</dd>
<dd>Accept-Language</dd>
<dd>Content-Language</dd>
<dd>Last-Event-ID</dd>
<dd>Content-Type:只限于三个值<br/>= <code>application/x-www-form-urlencoded</code><br/>= <code>multipart/form-data</code><br/>= <code>text/plain</code></dd>
</dl>
<p>这是为了兼容表单(form),因为历史上表单一直可以发出跨域请求。AJAX 的跨域设计就是,只要表单可以发,AJAX 就可以直接发。</p>
<p>凡是不同时满足上面两个条件,就属于非简单请求。</p>
<p>浏览器对这两种请求的处理,是不一样的。</p>
<h3>三、简单请求</h3>
<br/>
<h4>3.1 基本流程</h4>
<p>对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个<code>Origin</code>字段。</p>
<p>下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个<code>Origin</code>字段。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
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...
</pre>
<p>上面的头信息中,<code>Origin</code>字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。</p>
<p>如果<code>Origin</code>指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含<code>Access-Control-Allow-Origin</code>字段(详见下文),就知道出错了,从而抛出一个错误,被<code>XMLHttpRequest</code>的<code>onerror</code>回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。</p>
<p>如果<code>Origin</code>指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
</pre>
<p>上面的头信息之中,有三个与CORS请求相关的字段,都以<code>Access-Control-</code>开头。</p>
<dl>
<dt>(1)Access-Control-Allow-Origin</dt>
<dd>该字段是必须的。它的值要么是请求时<code>Origin</code>字段的值,要么是一个<code>*</code>,表示接受任意域名的请求。</dd>
<dt>(2)Access-Control-Allow-Credentials</dt>
<dd>该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为<code>true</code>,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为<code>true</code>,如果服务器不要浏览器发送Cookie,删除该字段即可。</dd>
<dt>(3)Access-Control-Expose-Headers</dt>
<dd>该字段可选。CORS请求时,<code>XMLHttpRequest</code>对象的<code>getResponseHeader()</code>方法只能拿到6个基本字段:<code>Cache-Control</code>、<code>Content-Language</code>、<code>Content-Type</code>、<code>Expires</code>、<code>Last-Modified</code>、<code>Pragma</code>。如果想拿到其他字段,就必须在<code>Access-Control-Expose-Headers</code>里面指定。上面的例子指定,<code>getResponseHeader('FooBar')</code>可以返回<code>FooBar</code>字段的值。</dd>
</dl>
<h4>3.2 withCredentials 属性</h4>
<p>上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定<code>Access-Control-Allow-Credentials</code>字段。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Credentials: true
</pre>
<p>另一方面,开发者必须在AJAX请求中打开<code>withCredentials</code>属性。</p>
<pre class="js:nogutter:nocontrols" name="code">
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
</pre>
<p>否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。</p>
<p>但是,如果省略<code>withCredentials</code>设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭<code>withCredentials</code>。</p>
<pre class="js:nogutter:nocontrols" name="code">
xhr.withCredentials = false;
</pre>
<p>需要注意的是,如果要发送Cookie,<code>Access-Control-Allow-Origin</code>就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的<code>document.cookie</code>也无法读取服务器域名下的Cookie。</p>
<h3>四、非简单请求</h3>
<br/>
<h4>4.1 预检请求</h4>
<p>非简单请求是那种对服务器有特殊要求的请求,比如请求方法是<code>PUT</code>或<code>DELETE</code>,或者<code>Content-Type</code>字段的类型是<code>application/json</code>。</p>
<p>非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。</p>
<p>浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的<code>XMLHttpRequest</code>请求,否则就报错。</p>
<p>下面是一段浏览器的JavaScript脚本。</p>
<pre class="js:nogutter:nocontrols" name="code">
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
</pre>
<p>上面代码中,HTTP请求的方法是<code>PUT</code>,并且发送一个自定义头信息<code>X-Custom-Header</code>。</p>
<p>浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
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...
</pre>
<p>"预检"请求用的请求方法是<code>OPTIONS</code>,表示这个请求是用来询问的。头信息里面,关键字段是<code>Origin</code>,表示请求来自哪个源。</p>
<p>除了<code>Origin</code>字段,"预检"请求的头信息包括两个特殊字段。</p>
<dl>
<dt>(1)Access-Control-Request-Method</dt>
<dd>该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是<code>PUT</code>。</dd>
<dt>(2)Access-Control-Request-Headers</dt>
<dd>该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是<code>X-Custom-Header</code>。</dd>
</dl>
<h4>4.2 预检请求的回应</h4>
<p>服务器收到"预检"请求以后,检查了<code>Origin</code>、<code>Access-Control-Request-Method</code>和<code>Access-Control-Request-Headers</code>字段以后,确认允许跨源请求,就可以做出回应。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
</pre>
<p>上面的HTTP回应中,关键的是<code>Access-Control-Allow-Origin</code>字段,表示<code>http://api.bob.com</code>可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Origin: *
</pre>
<p>如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被<code>XMLHttpRequest</code>对象的<code>onerror</code>回调函数捕获。控制台会打印出如下的报错信息。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
</pre>
<p>服务器回应的其他CORS相关字段如下。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
</pre>
<dl>
<dt>(1)Access-Control-Allow-Methods</dt>
<dd>该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。</dd>
<dt>(2)Access-Control-Allow-Headers</dt>
<dd>如果浏览器请求包括<code>Access-Control-Request-Headers</code>字段,则<code>Access-Control-Allow-Headers</code>字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。</dd>
<dt>(3)Access-Control-Allow-Credentials</dt>
<dd>该字段与简单请求时的含义相同。</dd>
<dt>(4)Access-Control-Max-Age</dt>
<dd>该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。</dd>
</dl>
<h4>4.3 浏览器的正常请求和回应</h4>
<p>一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个<code>Origin</code>头信息字段。服务器的回应,也都会有一个<code>Access-Control-Allow-Origin</code>头信息字段。</p>
<p>下面是"预检"请求之后,浏览器的正常CORS请求。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
</pre>
<p>上面头信息的<code>Origin</code>字段是浏览器自动添加的。</p>
<p>下面是服务器正常的回应。</p>
<pre class="cfg:nogutter:nocontrols" name="code">
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
</pre>
<p>上面头信息中,<code>Access-Control-Allow-Origin</code>字段是每次回应都必定包含的。</p>
<h3>五、与JSONP的比较</h3>
<p>CORS与JSONP的使用目的相同,但是比JSONP更强大。</p>
<p>JSONP只支持<code>GET</code>请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。</p>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-46709306733624221822020-08-04T19:09:00.001+08:002023-02-25T18:22:21.747+08:00利用 redirect 跳轉到 預設頁 或 預設查詢在網站開發有一些技巧可以增加後續的維護性,利用重導向來做預設內容的處裡,這樣可以統一集中的進行控制。<br />
<br />
預設頁面最常會因為業務的策略因素進行調整,這時候散落在各地的連結都要調整,費工又容易漏。<br />
<br />
預設查詢這個麻煩點在於參數會變動,散落在各地的連結一樣是個麻煩。<br />
<br />
<br />
但重導向這種方式也是有損失的,將會多一個 HTTP 請求,這對 UI 反應速度很要求的狀況來說,並不是一個好方法,可能就要改用統一網址管理來處理。<br />
<br />
<br />
利用 MVC 的 Index() 來控制預設頁面,Index 將不會有實體頁面,而是用來進行重導向。<br />
<pre class="cs" name="code">public class UserController : Controller
{
public ActionResult Index()
{
return RedirectToAction(nameof(List));
}
public ActionResult List(DateTime? date)
{
//...
return View();
}
}
</pre><br />
<br />
判斷 QueryString 為空時,進行預設查詢的重導向,以 QueryString 為判斷點的好處是有時候就是要查詢全部資料,這樣就不會被預設查詢卡到。<br />
<pre class="cs" name="code">public class UserController : Controller
{
public ActionResult List(DateTime? date)
{
if (Request.QueryString.Count == 0)
{
RedirectToAction(nameof(List), new
{
date = DateTime.Today.ToString("yyyy-MM-dd")
});
}
//...
return View();
}
}
</pre><br />
<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-33163877699696296622014-03-05T23:38:00.001+08:002023-02-25T21:32:20.715+08:00利用 HTTP Status Codes 傳遞 Ajax 成功失敗的狀態一般處理 Ajax 回應時會傳送的資訊種類有:資料、成功訊息、錯誤訊息、失敗訊息以及處理狀態,傳遞的資訊種類並不一致,再加上除了資料之外,通常還希望能傳遞處理狀態,這種情況大部分會選擇是以 JSON 的方式傳遞這兩個訊息,以下是常見的幾種格式:<br />
<br />
<pre class="js" name="code">{ code: 1, msg: "OK" }
{ success: true, result: "data", errorMsg: "" }
{ status: 'success', result: [], errorMsg: "" }
//...
</pre><br />
但以執行狀態跟操作行為作一個歸納,可以區分以下幾種回傳結果:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"> <tr class="header">
<th>資料操作</th>
<th>HTTP Method</th>
<th>成功</th>
<th>錯誤/失敗</th>
</tr>
<tr>
<td>檢視(Read)</td>
<td>GET</td>
<td>資料</td>
<td>錯誤/失敗訊息</td>
</tr>
<tr>
<td>新增(Create)<br />
修改(Update)<br />
刪除(Delete)</td>
<td>POST</td>
<td>成功訊息</td>
<td>錯誤/失敗訊息</td>
</tr>
</table><br />
從上面的歸納可以看出規律性,接著只要有方法可以傳送處理的狀態,以及能夠區分資料的種類,其實就單純很多,而 HTTP Status Codes 就是用來傳遞 HTTP 的處理狀態,如果利用這個方式來傳遞自訂的處理狀態,這樣 HTTP Content 就可以很單純傳遞資料,讓資料格式不受限於 JSON,還可以使用其他格式(text, xml, html),而且 XMLHttpRequest 本身就有處理 HTTP Status Codes 的能力,而 jQuery.ajax 也有提供 error status 的處理,所以可以利用這個來定義狀態的處理,在 HTTP Status Codes 有幾個已經定義狀態,很適合用來回傳處理狀態的資訊:<br />
<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"> <tr>
<th>400</th>
<td>Bad Request</td>
<td>錯誤的請求</td>
<td>適用在表單內容的錯誤,如必填欄位未填、Email 格式錯誤</td>
</tr>
<tr>
<th>403</th>
<td>Forbidden</td>
<td>沒有權限,被禁止的</td>
<td>適用在沒有登入或權限不足</td>
</tr>
<tr>
<th>500</th>
<td>Internal Server Error</td>
<td>內部服務器錯誤</td>
<td>適用在程式的錯誤</td>
</tr>
</table><br />
<br />
jQuery 接收資訊的範例<br />
<pre class="js" name="code">$.ajax({
type: "POST",
url: document.location,
success: function (data, textStatus, jqXHR) {
alert(data);
},
error: function (jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText);
}
});
</pre> <br />
<br />
PHP 傳遞錯誤訊息的範例 <br />
<pre class="php" name="code">if (php_sapi_name() == 'cgi'){
header("Status: 400 Bad Request");
}else{
header("HTTP/1.0 400 Bad Request");
}
exit("儲存失敗!!");
</pre><br />
<br />
C# MVC 傳遞錯誤訊息的範例 <br />
<pre class="cs" name="code">Response.TrySkipIisCustomErrors = true;
Response.StatusCode = 400;
return Content("儲存失敗!!");
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-68927165407301395562014-02-13T23:54:00.000+08:002014-02-14T12:29:56.438+08:00HTTP GET 與 POST 的比較及使用時機<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"> <th> </th> <th>GET</th> <th>POST</th> </tr>
<tr> <td style="white-space:nowrap;">瀏覽器歷史紀錄</td> <td>參數都會紀錄,因為都是URL的一部分</td> <td>參數都不會紀錄</td> </tr>
<tr> <td style="white-space:nowrap;">加入書籤</td> <td>參數都會紀錄,因為都是URL的一部分</td> <td>參數都不會紀錄</td> </tr>
<tr> <td style="white-space:nowrap;">回上一頁/<br />
重新載入</td> <td>GET請求是重新執行,但被存儲在瀏覽器的快取,則不被重新提交到服務器</td> <td>數據將被重新提交(瀏覽器通常會警告使用者該數據將需要重新提交)</td> </tr>
<tr> <td style="white-space:nowrap;">編碼類型</td> <td>application/x-www-form-urlencoded</td> <td>multipart/form-data 或 application/x-www-form-urlencoded,使用多編碼的二進制數據<br />
</td> </tr>
<tr> <td style="white-space:nowrap;">參數大小限制</td> <td>受限於 QueryString 長度限制(不超過 2KB 是最保險的,有些瀏覽器可以允許多達 64KB)</td> <td>允許大量傳輸,包括上傳文件到服務器</td> </tr>
<tr> <td style="white-space:nowrap;">參數傳輸方式</td> <td>QueryString</td> <td>POST Data(message-body)</td> </tr>
<tr> <td style="white-space:nowrap;">安全性</td> <td>容易破解,因為參數是網址的一部分,所以它被紀錄在瀏覽器歷史記錄和明文服務器日誌</td> <td>比較難破解</td> </tr>
<tr> <td style="white-space:nowrap;">使用性</td> <td>不應該被使用在發送密碼或其他敏感信息上</td> <td>使用在發送密碼或其他敏感信息上</td> </tr>
<tr> <td style="white-space:nowrap;">能見度</td> <td>GET方法是對所有人可見(它會顯示在瀏覽器的地址欄)</td> <td>POST方法變量不顯示在URL中</td> </tr>
<tr> <td style="white-space:nowrap;">執行速度</td> <td>快,GET 比 POST 快 1.5 倍</td> <td>慢,POST 多出需要發送數據的步驟</td> </tr>
<tr> <td style="white-space:nowrap;">快取</td> <td>瀏覽器會依據網址來快取資料,不同的網址有不同的快取</td> <td>瀏覽器不會快取</td> </tr>
<tr> <td style="white-space:nowrap;">自動重送</td> <td>會,在回應過長時會重發請求,直到重試結束</td> <td>不會,一個請求發出後會一直等待回應</td> </tr>
<tr> <td style="white-space:nowrap;">適用行為</td> <td>檢視(Read)</td> <td>新增(Create)、修改(Update)、刪除(Delete)</td> </tr>
<tr> <td style="white-space:nowrap;">情況包括</td> <td>透過 <link href="">、<img src="">、<script src="">、<iframe src=""> 額外載入的 JavaScritp、CSS、圖片</td> <td>透過 <form method="post"> 以及 Ajax post 發送的請求</td> </tr>
</table><br />
<br />
GET 適合用在「<b>檢視(Read)</b>」的操作行為,由於檢視資料的操作會遠比資料異動來的更頻繁,所以需要更快的回應,而且有快取可以加快二次檢視,參數在網址上可以使每一個網址都代表一個網頁,在加入書籤的連結能夠返回對應的網頁,再者所需要的參數很少(例如:id=122&type=1),對於資料異動後快取沒更新的問題,可以在 QueryString 加上資料最後修改的時間戳記(例如:id=122&type=1&t=1392183597718)。<br />
<br />
<br />
POST 適合用在「<b>新增(Create)</b>、<b>修改(Update)</b>、<b>刪除(Delete)</b>」的操作行為,資料異動所需要傳送的參數很可能超過 QueryString 的限制,不適合用 GET 來處理資料異動的傳送,GET 在等待過久會重新發送請求,這會造成重複的請求,如果在新增儲存就多新增一筆資料,而 POST 在一個請求發出後會一直等待回應,這可以保障在傳送的過程中請求是唯一的,新增資料的請求如果被記錄在書籤或歷史紀錄中,使用者點擊連結網址就新增一筆資料,這真是一件恐怖的事,所以在資料異動上的請求必須使用 POST 來傳送。<br />
<br />
<br />
參考來源:<br />
<a target="_blank" href="http://www.diffen.com/difference/GET_%28HTTP%29_vs_POST_%28HTTP%29">GET vs POST - Difference and Comparison | Diffen</a><br />
<a target="_blank" href="http://www.w3schools.com/tags/ref_httpmethods.asp">HTTP Methods: GET vs. POST</a><br />
<a target="_blank" href="http://www.oncoding.cn/2009/ajax-get-post/">寻根究底:Ajax请求的GET与POST方式比较</a><br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com2