摘要:由于瀏覽器同源策略,凡是發(fā)送請(qǐng)求的協(xié)議域名端口三者之間任意一與當(dāng)前頁(yè)面地址不同即為跨域最近項(xiàng)目要兼容,找了一些資料,實(shí)踐了一下,現(xiàn)在總結(jié)一下,避免以后踩坑。解決方案,微軟在和下給我們提供了來進(jìn)行解決跨域問題,官方的文檔可以在這里看到。
由于瀏覽器同源策略,凡是發(fā)送請(qǐng)求url的協(xié)議、域名、端口三者之間任意一與當(dāng)前頁(yè)面地址不同即為跨域
最近項(xiàng)目要兼容IE9,找了一些資料,實(shí)踐了一下,現(xiàn)在總結(jié)一下,避免以后踩坑。
普通請(qǐng)求的跨域 簡(jiǎn)單粗暴的解決方案第一次碰到這個(gè)問題,所以就是上網(wǎng)找找有沒有什么好的解決方案。最初找到的方案是這樣的,直接在IE中設(shè)置設(shè)置受信任的站點(diǎn),然后允許其可以進(jìn)行跨域訪問,最后在jQuery中設(shè)置開啟跨域請(qǐng)求。oh,No!這么粗暴,好吧,這也是一個(gè)不是辦法的辦法,如果做的是一個(gè)小項(xiàng)目,用戶不多,那直接寫在用戶手冊(cè)里,讓他們自己去配吧。但是,這顯然不是一個(gè)好的解決方案啊,那只能繼續(xù)找了。
XDomainRequest(XDR)解決方案ok,微軟在IE8和IE9下給我們提供了XDomainRequest來進(jìn)行解決跨域問題,官方的文檔可以在 這里看到。當(dāng)然Github上也有開源的jQuery插件,可以在這里找到。
XDR的限制:
XDR僅支持GET與POST這兩種請(qǐng)求方式,雖然可以使用上面提交的插件來解決前端部分只要進(jìn)行簡(jiǎn)單修改代碼就可以提交PUT/HEAD/DELETE的請(qǐng)求的問題,但是其請(qǐng)求的發(fā)生出去依舊還是將PUT/HEAD/DELETE轉(zhuǎn)化為POST,將HEAD 轉(zhuǎn)化為GET請(qǐng)求。當(dāng)是POST請(qǐng)求的時(shí)候,請(qǐng)求方案會(huì)以__method=原請(qǐng)求的方式結(jié)構(gòu)加入到請(qǐng)求體的body中。當(dāng)是HEAD 請(qǐng)求的時(shí)候,請(qǐng)求方案會(huì)以__method=原請(qǐng)求的方式結(jié)構(gòu)加入請(qǐng)求url的查詢參數(shù)中。現(xiàn)在大部分API開發(fā)都是按照RESTful規(guī)范進(jìn)行設(shè)計(jì)的,如果是自己的服務(wù)端還好,可以叫服務(wù)端的同學(xué)添加一個(gè)攔截器做一個(gè)攔截判斷,然后執(zhí)行對(duì)應(yīng)的方法(ps:我想過去應(yīng)該是這個(gè)樣子,不知道服務(wù)端的同學(xué)會(huì)不會(huì)磨刀子)。但是如果你調(diào)用是網(wǎng)上的API的接口的話,那就愛莫能助了。
XDR不支持自定義的請(qǐng)求頭,因此如果你的服務(wù)端是用過header中的自定義參數(shù)進(jìn)行做身份驗(yàn)證的話,那也行不通了。
請(qǐng)求頭的Content-Type只允許設(shè)置為text/plain
XDR不允許跨協(xié)議的請(qǐng)求,如果你的網(wǎng)頁(yè)是在HTTP協(xié)議下,那么你只能請(qǐng)求HTTP協(xié)議下的接口,不能訪問HTTPS 下的接口。
XDR只接受HTTP/HTTPS 的請(qǐng)求
發(fā)起請(qǐng)求的時(shí)候,不會(huì)攜帶authentication 或 cookies
JSONPJSONP的本質(zhì)是動(dòng)態(tài)的加載 標(biāo)簽,因此其只支持GET請(qǐng)求而不支持其他類型的HTTP請(qǐng)求。
JSONP 的執(zhí)行過程大致如下:
客戶端設(shè)置一個(gè)全局的function,然后使用callback=function 的方法,將回調(diào)的方法傳遞給服務(wù)端。例如:
// 定義全局函數(shù) function showData (data) { console.log(data) } var url = "http://test.com/jsonp/query?id=1&callback=showData" // 這個(gè)就是script標(biāo)簽中的url
服務(wù)端在接收到請(qǐng)求的時(shí)候,生成一個(gè)動(dòng)態(tài)的js腳本,在該腳本中,調(diào)用callback參數(shù)傳遞進(jìn)來的function,將回來返回的json 數(shù)據(jù)已參數(shù)的形式去傳遞給該function,這樣,客戶端在加載這個(gè)js的時(shí)候,就會(huì)自動(dòng)去執(zhí)行了。
代理其實(shí),跨域的根本問題就在于,你調(diào)用的服務(wù)端地址web地址不在同一個(gè)域下,那么,我們最容易想到的一個(gè)解決方案就是:那我把他們放在一個(gè)域下面不就可以了么。因此我們可以在web工程下 放置一個(gè)代理服務(wù)器,在IE10以下的瀏覽器中,我們的網(wǎng)絡(luò)請(qǐng)求統(tǒng)一走這一個(gè)代理接口,由服務(wù)器帶我們?nèi)マD(zhuǎn)發(fā)這個(gè)HTTP請(qǐng)求,然后再將結(jié)果返回給我們。
事實(shí)上我們項(xiàng)目中也是采用的這個(gè)方案,我們定義了一個(gè)接口:
URL: v0.1/dispatcher
方法: POST
請(qǐng)求內(nèi)容:
{ "request_url":"http://test.com", //必填,請(qǐng)求url "request_method":"POST", //必填,請(qǐng)求方法:GET/PUT/PATCH/POST/DELETE "request_headers":{ "Content-Type":["application/json"] }, //選填,請(qǐng)求頭 "request_data":{ "data":{ //請(qǐng)求body } } } //選填,請(qǐng)求body
服務(wù)端通過客戶端傳來的這些參數(shù)去構(gòu)造一個(gè)HttpClient ,發(fā)起請(qǐng)求。
文件上傳的問題既然通過上面的代理接口解決了,IE10 一下的跨域請(qǐng)求問題,本想著應(yīng)該沒什么問題了,試了試項(xiàng)目中的文件上傳,oh,no!不能運(yùn)行,看了看我們的文件上傳,是通過自己new FormData()的方式去向服務(wù)器POST請(qǐng)求的。然后翻找了一下webApi, 發(fā)現(xiàn)從IE10 開始兼容的,這就......,并且XMLHttpRequest的send(formData)這個(gè)方法也是從IE10開始支持的。那沒辦法了只能尋找其他的辦法了。
隱式表單上傳找到老司機(jī),請(qǐng)教了一下,早期IE都是用使用隱式的iframe中包含一個(gè)form表單,然后直接去提交form表單。然后服務(wù)完全返回的數(shù)據(jù)在iframe中,通過js代碼去里面獲取iframe中的數(shù)據(jù),作為返回值。
然后從老司機(jī)那邊得到一份插件ajaxfileupload,還有一個(gè)就是自己在Github上找的一個(gè)jQuery-File-Upload,現(xiàn)在就來講講這兩個(gè)插件
ajaxfileupload適用于服務(wù)器返回的數(shù)據(jù)是文本格式
這份代碼也很簡(jiǎn)單就200多行,主要就思想就是根據(jù)上面說的,使用隱式的iframe嵌套form表單來完成上傳操作。但是呢?這個(gè)插件只適合在服務(wù)器返回?cái)?shù)據(jù)是文本數(shù)據(jù)的時(shí)候,如果服務(wù)器返回的是json 的數(shù)據(jù),IE10一下的瀏覽器就會(huì)自動(dòng)去執(zhí)行下載操作,js代碼在執(zhí)行到下載的時(shí)候就中斷了,并不會(huì)繼續(xù)往下執(zhí)行了。所以也不是很適用。如果服務(wù)器支持返回?cái)?shù)據(jù)格式是文本格式的話,這個(gè)組件還是挺好用的。
// 基本用法如下 //選擇文件之后執(zhí)行上傳 $("#fileUpload").on("change", function() { $.ajaxFileUpload({ url:"http://test.com", secureuri:false, fileElementId:"fileToUpload",//file標(biāo)簽的id dataType: "json",//返回?cái)?shù)據(jù)的類型 data:{name:"logan"},//一同上傳的數(shù)據(jù) success: function (data, status) { console.log(data) }, error: function (data, status, e) { alert(e); } }); });jQuery-File-Upload
適用于服務(wù)器返回的數(shù)據(jù)是JSON格式切支持重定向
這個(gè)插件呢,對(duì)比ajaxfileupload他考慮到了這種返回json的情況,但是它的使用需要服務(wù)端進(jìn)行支持,其主要思想還是使用了隱式的表單上傳文件,但是它是通過服務(wù)其的重定向來接收數(shù)據(jù)的,服務(wù)器接收到了客戶端的請(qǐng)求之后,將返回的數(shù)據(jù)通過URLEncode之后,拼接在前端web頁(yè)面的后面,然后在頁(yè)面中解析數(shù)據(jù),寫到body中,用jQuery去獲取這些數(shù)據(jù)。
具體用法如下:
現(xiàn)在服務(wù)器構(gòu)造一個(gè)接受返回?cái)?shù)據(jù)的頁(yè)面result.html
result
然后自己定義一個(gè)上傳的組件,我這里是使用Vue來包裝成一個(gè)組件的
這個(gè)插件是依賴jQuery的,并且依賴jQuery-UI ,還有要注意的是在IE10以下的版本都要引入jquery.iframe-transport
與jquery.xdr-transport
我代碼中發(fā)送數(shù)據(jù)的方式是它在add 方法中返回的data數(shù)據(jù),通過該對(duì)象去直接上傳文件,這時(shí)上傳的FormData的文件信息中,文件原本是什么類型就是什么類型了,這是我們所期望的。我之前查看官方的文檔,還使用過另一種方式
var jqXHR = $("#fileupload").fileupload("send", {files: filesList}) .success(function (result, textStatus, jqXHR) {/* ... */}) .error(function (jqXHR, textStatus, errorThrown) {/* ... */}) .complete(function (result, textStatus, jqXHR) {/* ... */});
上傳的時(shí)候使用的是這樣的方式,發(fā)現(xiàn)FormData中上傳文件的類型變?yōu)榱?b>Content-Type: application/octet-stream,然后服務(wù)器就解析不到數(shù)據(jù)了。所以還是推薦用它原生的submit方式去提交數(shù)據(jù)。
注意
這兩個(gè)插件的本質(zhì)還是使用form表單上傳文件,因此我們無法添加自定義的header頭,并且如果原來的服務(wù)器不支持請(qǐng)求重定向的話怎么辦,那就沒有辦法使用jQuery-File-Upload這個(gè)插件了。所以最穩(wěn)妥的方式,還是在我們本地做了一層代理,由代理去發(fā)生真正的請(qǐng)求。
下面給出主要的轉(zhuǎn)發(fā)FormData的java代碼
public ResponseEntity dispatcherUpload(HttpServletRequest request) throws UnsupportedEncodingException { String requestUrl = request.getParameter("request_url"); String redirectUrl = request.getParameter("redirect"); String fileName = request.getParameter("name"); if (StringUtils.isEmpty(requestUrl) || StringUtils.isEmpty(redirectUrl)) throw new BizException(ErrorCode.INVALID_ARGUMENT); HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(requestUrl); String auth = request.getParameter("authorization"); if (!StringUtils.isEmpty(auth)) httpPost.addHeader("Authorization", request.getParameter("authorization").toString()); MultipartEntity reqEntity = new MultipartEntity(); if (!StringUtils.isEmpty(request.getParameter("path"))) { StringBody pathBody = new StringBody(request.getParameter("path")); reqEntity.addPart("path", pathBody); } if (!StringUtils.isEmpty(request.getParameter("scope"))) { StringBody scopeBody = new StringBody(request.getParameter("scope")); reqEntity.addPart("scope", scopeBody); } if (!StringUtils.isEmpty(request.getParameter("expireDays"))) { StringBody expireDaysBody = new StringBody(request.getParameter("expireDays")); reqEntity.addPart("expireDays", expireDaysBody); } if (!StringUtils.isEmpty(fileName)) { StringBody nameBody = new StringBody(fileName); reqEntity.addPart("name", nameBody); } MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request; MultiValueMapmultiValueMap = multipartHttpServletRequest.getMultiFileMap(); //todo:現(xiàn)在暫時(shí)寫死,不去遍歷map if(!(multiValueMap.containsKey(CS_FILE_KEY) || multiValueMap.containsKey(UC_FILE_KEY))) throw new BizException(ErrorCode.INVALID_ARGUMENT); String fileKey = multiValueMap.containsKey(CS_FILE_KEY) ? CS_FILE_KEY : UC_FILE_KEY; MultipartFile multipartFile = multipartHttpServletRequest.getFile(fileKey); // 得到文件數(shù)據(jù) if (!multipartFile.isEmpty()) { CommonsMultipartFile commonsMultipartFile = (CommonsMultipartFile) multipartFile; DiskFileItem diskFileItem = (DiskFileItem) commonsMultipartFile.getFileItem(); String filePath = diskFileItem.getStoreLocation().getPath().toString(); File file = null; try { //判斷目錄是否已存在,如果filename不為空,將其帶入創(chuàng)建文件(真實(shí)還原文件類型,否則是.tmp臨時(shí)文件) if (StringUtils.isEmpty(fileName)) { file = new File(filePath); } else { file = new File(filePath, fileName); } if (!file.exists()) { file.mkdirs(); } //保存文件 multipartFile.transferTo(file); FileBody bin = new FileBody(file); reqEntity.addPart(fileKey, bin); httpPost.setEntity(reqEntity); HttpHeaders responseHeader = new HttpHeaders(); HttpResponse httpResponse = null; try { httpResponse = httpClient.execute(httpPost); } catch (Exception e) { LOG.error("代理文件上傳失敗,請(qǐng)求地址:{},請(qǐng)求內(nèi)容:{}", requestUrl, null, e); JSONObject failedJson = new JSONObject(); failedJson.put("result", "FAILURE"); failedJson.put("data", e.toString()); URI uri = URI.create(redirectUrl + e.toString()); responseHeader.setLocation(uri); return new ResponseEntity(responseHeader, HttpStatus.MOVED_TEMPORARILY); } LOG.info("狀態(tài)碼:" + httpResponse.getStatusLine().getStatusCode()); org.apache.http.HttpEntity httpEntity = httpResponse.getEntity(); //判斷請(qǐng)求是否成功 String responseBody = ""; String isSuccess = "SUCCESS"; if (httpResponse.getStatusLine().getStatusCode() >= HttpStatus.OK.value() && httpResponse.getStatusLine().getStatusCode() < HttpStatus.BAD_REQUEST.value()) { if (null != httpEntity) { // System.out.println("響應(yīng)內(nèi)容:" + EntityUtils.toString(httpEntity, ContentType.getOrDefault(httpEntity).getCharset())); responseBody = EntityUtils.toString(httpEntity, ContentType.getOrDefault(httpEntity).getCharset()); //處于安全考慮,關(guān)閉數(shù)據(jù)流 EntityUtils.consume(httpEntity); } } else { //上傳失敗(非2XX) isSuccess = "FAILURE"; } JSONObject ResJson = new JSONObject(); ResJson.put("result", isSuccess); ResJson.put("data", responseBody); URI uri = URI.create(redirectUrl + URLEncoder.encode(ResJson.toString(), "UTF-8")); responseHeader.setLocation(uri); return new ResponseEntity(responseHeader, HttpStatus.MOVED_TEMPORARILY); } catch (IOException e) { throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, e); } finally { if (file != null) { file.delete(); } } }else { throw new BizException(HttpStatus.BAD_REQUEST, "PORTAL-APP/INVALID_ARGUMENT", "上傳文件為空"); } }
在轉(zhuǎn)發(fā)文件的時(shí)候,我們做了一層轉(zhuǎn)存,原因在于,我們測(cè)試一個(gè)服務(wù)器的時(shí)候,我們直接使用一個(gè)緩存的數(shù)據(jù),去寫到FormData中,那邊服務(wù)器接收到的文件對(duì)象居然是空的,因此我們才做了一層緩存,用一個(gè)真實(shí)存在的文件去做。
---end---
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/86953.html
摘要:本文將針對(duì)使用生態(tài)開發(fā)完成的網(wǎng)站,以版本為基礎(chǔ)兼容目標(biāo),實(shí)現(xiàn)全功能正常使用的全面兼容解決方案。這樣做的目的,是逐步減少全局性方法,使得語言逐步模塊化。此外,使用這個(gè),一旦頁(yè)面不處于瀏覽器的當(dāng)前標(biāo)簽,就會(huì)自動(dòng)停止刷新。 前言 背景情況 vue - 2.5.11 vue-cli 使用模板 webpack-simple http請(qǐng)求:axios Vue 官方對(duì)于 ie 瀏覽器版本兼容情...
摘要:例外當(dāng)涉及到同源策略時(shí),有兩個(gè)主要的例外授信范圍兩個(gè)相互之間高度互信的域名,如公司域名,不遵守同源策略的限制。端口未將端口號(hào)加入到同源策略的組成部分之中,因此和屬于同源并且不受任何限制。 原文鏈接:http://www.devsai.com/2016/11/24/talk-CORS/ 同源策略(same origin policy) 1995年,同源政策由 Netscape 公司引入瀏...
摘要:可以說同源策略在安全中扮演著及其重要的角色。我把這個(gè)領(lǐng)域的東西寫成了一個(gè)系列,以后還會(huì)繼續(xù)完善下去安全一同源策略與跨域安全二攻擊安全三攻擊 之所以要將同源策略與跨域?qū)懺谝黄?,是因?yàn)榇嬖跒g覽器的同源策略,才會(huì)存在跨域問題 何為同源策略 同源策略是瀏覽器實(shí)現(xiàn)的一種安全策略,它限制了不同源之間的文檔和腳本交互的權(quán)限。只有同一個(gè)源的腳本才會(huì)具有操作dom、讀寫cookie、session 、a...
摘要:說明是否允許通訊同一域名允許同一域名下的不同文件夾允許不同端口號(hào)不允許不同協(xié)議不允許不同域名不允許主域相同,子域不同不允許跨域解決方案由于瀏覽器同源策略是允許標(biāo)簽這樣的跨域資源嵌套的,所以標(biāo)簽的資源不受同源策略的限制。 前言 本著學(xué)習(xí)和總結(jié)的態(tài)度寫的技術(shù)輸出,文中有任何錯(cuò)誤和問題,請(qǐng)大家指出。更多的技術(shù)輸出可以查看我的 github博客。 整理了一些前端的學(xué)習(xí)資源,希望能夠幫助到有需要...
閱讀 1439·2021-11-11 16:54
閱讀 9319·2021-11-02 14:44
閱讀 2371·2021-10-22 09:53
閱讀 3259·2019-08-30 11:18
閱讀 1951·2019-08-29 13:29
閱讀 2003·2019-08-27 10:58
閱讀 1623·2019-08-26 11:38
閱讀 3518·2019-08-26 10:31