Java EE 7 / JAX-RS 2.0 – REST上的CORS
Java EE REST應用程序通常在開箱即用的開發機器上運行良好,該開發機器上所有服務器端資源和客戶端UI均指向“ localhost”或127.0.0.1。 但是,當涉及跨域部署時(當REST客戶端不再與托管REST API的服務器位于同一域時),則需要一些解決方法。 本文是關于Java EE 7 / JAX-RS 2.0 REST API時如何使跨域或更廣稱為跨域資源共享(又稱為CORS)的。 本文無意討論有關瀏覽器和其他與安全性相關的機制,您可能會在其他網站上找到它。 但是我們真正想要在這里實現的是再次使事情盡快運作。
問題是什么?
演示Java EE 7(JAX-RS 2.0)REST服務
在本文中,我將僅為演示目的而編寫一個基于Java EE 7 JAX-RS 2.0的簡單REST Web服務和客戶端。
在這里,我將定義一個接口,用REST服務的url路徑對其進行注釋,以及HTTP響應接受的HTTP方法和MIME類型。
RESTCorsDemoResourceProxy.java的代碼:
package com.developerscrappad.intf;import java.io.Serializable; import javax.ejb.Local; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response;@Local @Path( "rest-cors-demo" ) public interface RESTCorsDemoResourceProxy extends Serializable {@GET@Path( "get-method" )@Produces( MediaType.APPLICATION_JSON )public Response getMethod();@PUT@Path( "put-method" )@Produces( MediaType.APPLICATION_JSON )public Response putMethod();@POST@Path( "post-method" )@Produces( MediaType.APPLICATION_JSON )public Response postMethod();@DELETE@Path( "delete-method" )@Produces( MediaType.APPLICATION_JSON )public Response deleteMethod(); }RESTCorsDemoResource.java的代碼:
package com.developerscrappad.business;import com.developerscrappad.intf.RESTCorsDemoResourceProxy; import javax.ejb.Stateless; import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; import javax.ws.rs.core.Response;@Stateless( name = "RESTCorsDemoResource", mappedName = "ejb/RESTCorsDemoResource" ) public class RESTCorsDemoResource implements RESTCorsDemoResourceProxy {@Overridepublic Response getMethod() {JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "message", "get method ok" );JsonObject jsonObj = jsonObjBuilder.build();return Response.status( Response.Status.OK ).entity( jsonObj.toString() ).build();}@Overridepublic Response putMethod() {JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "message", "get method ok" );JsonObject jsonObj = jsonObjBuilder.build();return Response.status( Response.Status.ACCEPTED ).entity( jsonObj.toString() ).build();}@Overridepublic Response postMethod() {JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "message", "post method ok" );JsonObject jsonObj = jsonObjBuilder.build();return Response.status( Response.Status.CREATED ).entity( jsonObj.toString() ).build();}@Overridepublic Response deleteMethod() {JsonObjectBuilder jsonObjBuilder = Json.createObjectBuilder();jsonObjBuilder.add( "message", "delete method ok" );JsonObject jsonObj = jsonObjBuilder.build();return Response.status( Response.Status.ACCEPTED ).entity( jsonObj.toString() ).build();} }RESTCorsDemoResource中的代碼簡單明了,但請記住,這只是一個演示應用程序,在其業務邏輯中沒有有效的目的。 RESTCorsDemoResource類實現在接口RESTCorsDemoResourceProxy中定義的方法簽名。 它有幾種方法可以通過特定的HTTP方法(例如GET,PUT,POST和DELETE)處理傳入的HTTP請求,并且在方法結束時,該過程完成后會返回一條簡單的JSON消息。
別忘了下面的web.xml ,當路徑檢測到“ / rest-api / * ”(例如http:// <host>:<port>)時,該web.xml會告訴應用服務器將其視為任何傳入HTTP請求的REST API調用。 / AppName / rest-api / get-method /)。
web.xml中的內容:
javax.ws.rs.core.Application1javax.ws.rs.core.Application/rest-api/*部署方式
讓我們將以上內容打包到一個名為RESTCorsDemo.war的war文件中,并將其部署到與Java EE 7兼容的應用服務器。 在我這方面,我正在使用默認設置在Glassfish 4.0上運行此程序,該默認設置位于具有公共域的計算機上developerscrappad.com
部署后,REST服務的URL應如下所示:
| RESTCorsDemoResourceProxy.getMethod() | http://developerscrappad.com/RESTCorsDemo/rest-api/rest-cors-demo/get-method/ |
| RESTCorsDemoResourceProxy.postMethod() | http://developerscrappad.com/RESTCorsDemo/rest-api/rest-cors-demo/post-method/ |
| RESTCorsDemoResourceProxy.putMethod() | http://developerscrappad.com/RESTCorsDemo/rest-api/rest-cors-demo/put-method/ |
| RESTCorsDemoResourceProxy.deleteMethod() | http://developerscrappad.com/RESTCorsDemo/rest-api/rest-cors-demo/delete-method/ |
HTML REST客戶端
在本地計算機上,我將創建一個簡單HTML頁面,以通過以下方式調用已部署的REST服務器資源:
rest-test.html的代碼:
<!DOCTYPE html> <html><head><title>REST Tester</title><meta charset="UTF-8"></head><body><div id="logMsgDiv"></div><script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script><script type="text/javascript">var $ = jQuery.noConflict();$.ajax( {cache: false,crossDomain: true,dataType: "json",url: "http://developerscrappad.com:8080/RESTCorsDemo/rest-api/rest-cors-demo/get-method/",type: "GET",success: function( jsonObj, textStatus, xhr ) {var htmlContent = $( "#logMsgDiv" ).html( ) + "<p>" + jsonObj.message + "</p>";$( "#logMsgDiv" ).html( htmlContent );},error: function( xhr, textStatus, errorThrown ) {console.log( "HTTP Status: " + xhr.status );console.log( "Error textStatus: " + textStatus );console.log( "Error thrown: " + errorThrown );}} );$.ajax( {cache: false,crossDomain: true,dataType: "json",url: "http://developerscrappad.com:8080/RESTCorsDemo/rest-api/rest-cors-demo/post-method/",type: "POST",success: function( jsonObj, textStatus, xhr ) {var htmlContent = $( "#logMsgDiv" ).html( ) + "<p>" + jsonObj.message + "</p>";$( "#logMsgDiv" ).html( htmlContent );},error: function( xhr, textStatus, errorThrown ) {console.log( "HTTP Status: " + xhr.status );console.log( "Error textStatus: " + textStatus );console.log( "Error thrown: " + errorThrown );}} );$.ajax( {cache: false,crossDomain: true,dataType: "json",url: "http://developerscrappad.com:8080/RESTCorsDemo/rest-api/rest-cors-demo/put-method/",type: "PUT",success: function( jsonObj, textStatus, xhr ) {var htmlContent = $( "#logMsgDiv" ).html( ) + "<p>" + jsonObj.message + "</p>";$( "#logMsgDiv" ).html( htmlContent );},error: function( xhr, textStatus, errorThrown ) {console.log( "HTTP Status: " + xhr.status );console.log( "Error textStatus: " + textStatus );console.log( "Error thrown: " + errorThrown );}} );$.ajax( {cache: false,crossDomain: true,dataType: "json",url: "http://developerscrappad.com:8080/RESTCorsDemo/rest-api/rest-cors-demo/delete-method/",type: "DELETE",success: function( jsonObj, textStatus, xhr ) {var htmlContent = $( "#logMsgDiv" ).html( ) + "<p>" + jsonObj.message + "</p>";$( "#logMsgDiv" ).html( htmlContent );},error: function( xhr, textStatus, errorThrown ) {console.log( "HTTP Status: " + xhr.status );console.log( "Error textStatus: " + textStatus );console.log( "Error thrown: " + errorThrown );}} );</script></body> </html>在這里,我將jQuery的ajax對象用于具有定義選項的REST Services調用。 rest-test.html的目的是使用適當的HTTP方法調用REST服務URL,并獲取響應作為JSON結果以供以后處理。 我在這里不做詳細介紹,但是如果您想進一步了解可用的$ .ajax調用選項,可以訪問jQuery的文檔站點 。
當我們運行rest-test.html時會發生什么?
當我在Firefox瀏覽器上運行rest-test.html文件時,配備了Firebug插件 ,下面是我得到的屏幕截圖。
屏幕快照:Firebug控制臺選項卡結果
屏幕快照:Firebug的“ Net”選項卡結果
如您所見,當我在控制臺選項卡上選中時,“ / rest-api / rest-cors-demo / get-method / ”和“ / rest-api / rest-cors-demo / post-method / ”返回了正確的HTTP狀態,但是我可以絕對確定該方法未在遠程Glassfish應用服務器上執行,REST服務調用只是被繞過,在rest-test.html客戶端上,它直接進入了$ .ajax錯誤回調。 當我如圖所示檢查Firebug網絡選項卡時,“ / rest-api / rest-cors-demo / put-method / ”和“ / rest-api / rest-cors-demo / delete-method / ”如何處理?在其中一個屏幕截圖中,瀏覽器通過觸發OPTIONS作為HTTP方法而不是PUT和DELETE發送了預檢請求。 這種現象與服務器端和瀏覽器的安全性有關。 我在頁面底部還編譯了與此相關的其他一些網站。
如何使CORS在Java EE 7 / JAX-RS 2.0中工作(通過攔截器)
為了在客戶端和服務器端REST資源上進行跨域調用或簡稱為CORS,我創建了兩個JAX-RS 2.0攔截器類,一個實現了ContainerRequestFilter ,另一個實現了ContainerResponseFilter 。
ContainerResponseFilter中的其他HTTP標頭
瀏覽器將需要一些其他HTTP標頭來響應它,以進一步驗證服務器端資源是否允許跨域/跨域資源共享,以及允許的安全級別或限制級別。 這些標頭在啟用CORS時開箱即用。
Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET, POST, DELETE, PUT這些額外的HTTP標頭集可以通過將其包含在實現ContainerResponseFilter的類中而返回到瀏覽器時,作為HTTP響應的一部分包含在內。
**但請注意:具有“訪問控制允許來源:*”將允許接受所有呼叫,而與客戶端的位置無關。 有多種方法可以進一步限制此限制,因為您只希望服務器端僅允許來自特定域的REST服務調用。 請查看頁面底部的相關文章。
RESTCorsDemoResponseFilter.java的代碼:
package com.developerscrappad.filter;import java.io.IOException; import java.util.logging.Logger; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.container.PreMatching; import javax.ws.rs.ext.Provider;@Provider @PreMatching public class RESTCorsDemoResponseFilter implements ContainerResponseFilter {private final static Logger log = Logger.getLogger( RESTCorsDemoResponseFilter.class.getName() );@Overridepublic void filter( ContainerRequestContext requestCtx, ContainerResponseContext responseCtx ) throws IOException {log.info( "Executing REST response filter" );responseCtx.getHeaders().add( "Access-Control-Allow-Origin", "*" );responseCtx.getHeaders().add( "Access-Control-Allow-Credentials", "true" );responseCtx.getHeaders().add( "Access-Control-Allow-Methods", "GET, POST, DELETE, PUT" );} }處理瀏覽器預檢請求HTTP方法:選項
實現ContainerResponseFilter的RESTCorsDemoResponseFilter類僅解決了部分問題。 我們仍然必須處理瀏覽器對PUT和DELETE HTTP方法的飛行前請求。 大多數流行的瀏覽器的基礎飛行前請求機制都以某種方式工作,即它們使用OPTIONS作為HTTP方法發送請求,只是為了測試水域。 如果服務器端資源確認請求的路徑URL,并允許接受PUT或DELETE HTTP方法進行處理,則服務器端通常將必須發送HTTP Status 200(OK)響應(或任何20x HTTP Status)在瀏覽器將實際請求作為HTTP方法PUT或DELETE之后發送給瀏覽器之前返回。 但是,此機制必須由開發人員手動實現。 因此,我以RESTCorsDemoRequestFilter的名稱實現了一個新類, 該類實現了以下針對此機制顯示的ContainerRequestFilter 。
RESTCorsDemoRequestFilter.java的代碼:
package com.developerscrappad.filter;import java.io.IOException; import java.util.logging.Logger; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.PreMatching; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider;@Provider @PreMatching public class RESTCorsDemoRequestFilter implements ContainerRequestFilter {private final static Logger log = Logger.getLogger( RESTCorsDemoRequestFilter.class.getName() );@Overridepublic void filter( ContainerRequestContext requestCtx ) throws IOException {log.info( "Executing REST request filter" );// When HttpMethod comes as OPTIONS, just acknowledge that it accepts...if ( requestCtx.getRequest().getMethod().equals( "OPTIONS" ) ) {log.info( "HTTP Method (OPTIONS) - Detected!" );// Just send a OK signal back to the browserrequestCtx.abortWith( Response.status( Response.Status.OK ).build() );}} }結果
之后, RESTCorsDemoResponseFilter和RESTCorsDemoRequestFilter包含在應用程序中并進行部署。 然后,我再次在瀏覽器中重新運行rest-test.html 。 結果,JAX-RS 2.0應用程序很好地處理了來自不同位置的具有不同HTTP方法GET,POST,PUT和DELETE的所有HTTP請求。 下面的屏幕截圖是我的瀏覽器成功執行的HTTP請求。 Firebug控制臺和NET Tab的這些結果是預期的:
屏幕快照:Firebug控制臺選項卡
屏幕截圖:Firebug的“ Net”選項卡
最后的話
在攔截諸如啟用CORS之類的方案的REST相關請求和響應時,JAX-RS 2.0攔截器非常方便。 如果您正在為Java項目使用REST庫的特定實現,例如Jersey或RESTEasy ,請檢查如何具體實現請求和響應攔截器,應用上述技術,您應該能夠獲得相同的結果。 相同的原理幾乎相同。
好吧,希望本文能幫助您解決Java EE 7 / JAX-RS 2.0 REST項目上的跨域或CORS問題。
感謝您的閱讀。
相關文章:
- http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
- http://www.html5rocks.com/zh-CN/tutorials/cors/
- http://www.w3.org/TR/cors/
- https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS
翻譯自: https://www.javacodegeeks.com/2014/11/java-ee-7-jax-rs-2-0-cors-on-rest.html
總結
以上是生活随笔為你收集整理的Java EE 7 / JAX-RS 2.0 – REST上的CORS的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑大全图片大全(电脑图片大全高清)
- 下一篇: 电脑桌面壁纸1920x1080超清小清新