五分鐘為HTTP接口提供Java/Scala SDK

wu317324 8年前發布 | 14K 次閱讀 Java HTTP Scala

來自: http://www.jianshu.com/p/9a845828177f

上次構建Spark 任務發布管理程序時,正好用到了兩個yarn的接口。因為不想引入Yarn的client包,所以使用了他的Http接口。那么如何調用這個HTTP接口便是一個問題了

</div>

Case描述

我現在要使用yarn的兩個接口,一個是application 列表,一個是根據appId獲取這個app的詳情。對應的接口大約如此:

</div>

基于HttpClient的初級封裝

基于HttpClient的一個典型封裝如下:

public interface HttpTransportService {

public SResponse post(Url url, Map data);

public SResponse post(Url url, Map data, Map<String, String> headers);

public SResponse post(final Url url, final Map data, final int timeout);

public SResponse post(final Url url, final Map data, final Map<String, String> headers, final int timeout);

public SResponse get(final Url url, final int timeout);

public SResponse get(Url url);

public SResponse get(Url url, Map<String, String> data);

public SResponse get(final Url url, final Map<String, String> data, final int timeout);

public SResponse put(Url url, Map data);</pre> 

更詳細的代碼可以參看我以前的開源項目里的實現: HttpTransportService.java

然而這種使用比較原始,不直觀。

大致使用方法如下:

SResponse  response = HttpTransportService.get(new Url("http://[dns]/ws/v1/cluster/apps/"+appId))
if(response.getStatus==200){
  //parse result like JSONObject.parse(response.getContent())
}else{
//blabla
}

所以一般如果提供了HTTP 接口的項目都會給你一個SDK,方便你做調用,幫你屏蔽掉HTTP的細節。

總體而言你有如下幾種選擇:

  1. 直接使用上面的封裝
  2. 使用第三方SDK
  3. 自己再做一些封裝,轉化為普通的方法調用

第三種方案一般會是這樣:

def app(appName:String):YarnApplication = {
// 實現http接口請求解析邏輯
}

雖然麻煩了些,但是調用者會比較幸福。

其實可以更簡單

只要三步,就可以實現第三個方案:

  1. 定義一個接口
  2. 獲取這個接口的引用
  3. 盡情使用

定義一個接口:

trait YarnController {

@At(path = Array("/ws/v1/cluster/apps"), types = Array(RestRequest.Method.GET)) def apps(@Param("states") states: String): java.util.List[HttpTransportService.SResponse]

@At(path = Array("/ws/v1/cluster/apps/{appId}"), types = Array(RestRequest.Method.GET)) def app(@Param("appId") appId: String): java.util.List[HttpTransportService.SResponse]

}</pre>

At 注解定義了 path路徑以及Action Method。 方法參數決定了傳遞的參數。

對于同一個http接口,你也可以定義多個方法。比如,

trait YarnController {

@At(path = Array("/ws/v1/cluster/apps"), types = Array(RestRequest.Method.GET)) def apps(@Param("states") states: String): java.util.List[HttpTransportService.SResponse]

@At(path = Array("/ws/v1/cluster/apps"), types = Array(RestRequest.Method.GET)) def runningApps(@Param("states") states: String="running"): java.util.List[HttpTransportService.SResponse]

}</pre>

這樣你直接調用runningApps 就可以拿到特定狀態的應用,而無需傳遞參數。如果參數較多,你還可以指定哪些參數不傳,哪些傳。

接著初始化 YarnController,獲得對象的引用,代碼如下:

val yarnRestClient: YarnController = 
AggregateRestClient.buildIfPresent[YarnController]
(hostAndPort, 
firstMeetProxyStrategy, 
RestClient.transportService)
  • hostAndPort 是yarn的地址
  • firstMeetProxyStrategy 指定如果后端有多個實例時的訪問策略
  • RestClient.transportService 就是我上面的最基礎的封裝HttpTransportService
  • </ul>

    理論上后面兩個參數可以不用傳遞

    這個時候你就可以直接使用獲得的 YarnController 對象了。具體使用方式如下:

    val result:java.util.List[HttpTransportService.SResponse] = yarnRestClient.apps()

    上面就是你要做的所有工作,系統自動幫你實現了HTTP調用。

    我希望返回結果是一個Bean

    前面的放回結果是個List[SResponse]對象。我希望它是個Bean對象。所以我定義一個Bean類:

    class YarnApplication(val id: String,
                                var user: String,
                                var name: String,
                                var queue: String,
                                var state: String,
                                var finalStatus: String,
                                var progress: Long,
                                var trackingUI: String,
                                var trackingUrl: String,
                                var diagnostics: String,
                                var clusterId: Long,
                                var applicationType: String,
                                var applicationTags: String,
                                var startedTime: Long,
                                var finishedTime: Long,
                                var elapsedTime: Long,
                                var amContainerLogs: String,
                                var amHostHttpAddress: String,
                                var allocatedMB: Long,
                                var allocatedVCores: Long,
                                var runningContainers: Long,
                                var memorySeconds: Long,
                                var vcoreSeconds: Long,
                                var preemptedResourceMB: Long,
                                var preemptedResourceVCores: Long,
                                var numNonAMContainerPreempted: Long,
                                var numAMContainerPreempted: Long)

    然后引入一個隱式轉換

    import ..... SReponseConvertor ._
    val result:Map[Map[String,List[YarnApplication]]]= yarnRestClient.apps().extract[Map[Map[String,List[YarnApplication]]]]

    result("apps")("app") //這樣就能拿到List[YarnApplication]</pre>

    SReponseConvertor 給List[SReponse]對象添加了一個新的extract 方法。當然前提是List[SReponse] 里是一個JSON格式的數據。

    因為yarn的接口返回的格式比較詭異,嵌套了兩層,第一層是apps,第二層是app,第三層才是具體的List對象。所以有了上面的復雜形態。那我如何簡化呢?每次調用都這么搞,太復雜了。

    那么自己實現一個隱式轉換就行了,定義一個YarnControllerE類,

    object YarnControllerE {
      implicit def mapSResponseToObject(response: java.util.List[HttpTransportService.SResponse]): SResponseEnhance = {
        new SResponseEnhance(response)
      }
    }

    import scala.collection.JavaConversions._

    class SResponseEnhance(x: java.util.List[HttpTransportService.SResponse]) {

    private def extractT(implicit manifest: Manifest[T]): T = { if (x == null || x.isEmpty || x(0).getStatus != 200) { return null.asInstanceOf[T] } implicit val formats = SJSon.DefaultFormats SJSon.parse(res).extract[T] }

    def list(): List[YarnApplication] = { val item = extractMap[String, Map[String, List[YarnApplication]]].getContent) return item("apps")("app") }</pre>

    現在你可以很帥氣這樣調用了:

    import ..... YarnControllerE ._
    val result: List[YarnApplication] = yarnRestClient.apps().list

    這樣我們就可以像RPC一樣訪問一個HTTP接口了。

    背后的機制

    核心代碼其實是這個:

    val yarnRestClient: YarnController = 
    AggregateRestClient.buildIfPresent[YarnController]
    (hostAndPort, 
    firstMeetProxyStrategy, 
    RestClient.transportService)

    AggregateRestClient 會幫你把YarnController 自動實現了。實現的機制很簡單就是 JDK 的 Proxy機制。具體源碼可以參看: AggrateRestClient.scala 以及 RestClientProxy.java

    </div>

 本文由用戶 wu317324 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!