2016-01-03 31 views
2

我建立一個REST API,它收集來自其他API的數據,做一些邏輯與它併發送回客戶端:Vertx拋出IllegalStateException異常:響應已被寫入

我的主類:

public class Main { 

public static void main(String[] args) { 

    Vertx.vertx().deployVerticle(RestVerticle.class.getName()); 
} 

這是我RestVerticle:

public class RestVerticle extends AbstractVerticle { 

public static final String API_V1 = "/api/v1"; 

private Map<String, JsonObject> products = new HashMap<>(); 

@Override 
public void start() { 

    Router router = Router.router(vertx); 

    router.route().handler(BodyHandler.create()); 
    router.get(API_V1 + "/business/all").handler(this::getAllBusinesses); 

    vertx.createHttpServer().requestHandler(router::accept).listen(8080); 
} 

private void getAllBusinesses(RoutingContext routingContext) { 

    vertx.deployVerticle(YelpClientVerticle.class.getName()); 
    MessageConsumer<String> consumer = vertx.eventBus().consumer("api"); 

    consumer.handler(message -> { 
     JsonObject m = new JsonObject((String) message.body()); 
     System.out.println("Received message: " + message.body()); 
     routingContext.response().putHeader("content-type", "application/json").end(m.encodePrettily()); 

    }); 
} 

}

這是我httpcl ient,這就要求對Yelp的API:

public class YelpClientVerticle extends AbstractVerticle { 

private static final String API_HOST = "api.yelp.com"; 
private static final String DEFAULT_TERM = "dinner"; 
private static final String DEFAULT_LOCATION = "San Francisco, CA"; 
private static final int SEARCH_LIMIT = 20; 
private static final String SEARCH_PATH = "/v2/search"; 
private static final String BUSINESS_PATH = "/v2/business"; 

/* 
* Update OAuth credentials below from the Yelp Developers API site: 
* http://www.yelp.com/developers/getting_started/api_access 
*/ 
private static final String CONSUMER_KEY = "XXXXX"; 
private static final String CONSUMER_SECRET = "XXXXX"; 
private static final String TOKEN = "XXXXX"; 
private static final String TOKEN_SECRET = "XXXXX"; 

OAuthService service; 
Token accessToken; 

/** 
* Setup the Yelp API OAuth credentials. 
* 
* @param consumerKey Consumer key 
* @param consumerSecret Consumer secret 
* @param token   Token 
* @param tokenSecret Token secret 
*/ 

/** 
* Creates and sends a request to the Search API by term and location. 
* <p> 
* See <a href="http://www.yelp.com/developers/documentation/v2/search_api">Yelp Search API V2</a> 
* for more info. 
* 
* @param term  <tt>String</tt> of the search term to be queried 
* @param location <tt>String</tt> of the location 
* @return <tt>String</tt> JSON Response 
*/ 
public String searchForBusinessesByLocation(String term, String location) { 
    OAuthRequest request = createOAuthRequest(SEARCH_PATH); 
    request.addQuerystringParameter("term", term); 
    request.addQuerystringParameter("location", location); 
    request.addQuerystringParameter("limit", String.valueOf(SEARCH_LIMIT)); 
    return sendRequestAndGetResponse(request); 
} 

/** 
* Creates and sends a request to the Business API by business ID. 
* <p> 
* See <a href="http://www.yelp.com/developers/documentation/v2/business">Yelp Business API V2</a> 
* for more info. 
* 
* @param businessID <tt>String</tt> business ID of the requested business 
* @return <tt>String</tt> JSON Response 
*/ 
public String searchByBusinessId(String businessID) { 
    OAuthRequest request = createOAuthRequest(BUSINESS_PATH + "/" + businessID); 
    return sendRequestAndGetResponse(request); 
} 

/** 
* Creates and returns an {@link OAuthRequest} based on the API endpoint specified. 
* 
* @param path API endpoint to be queried 
* @return <tt>OAuthRequest</tt> 
*/ 
private OAuthRequest createOAuthRequest(String path) { 
    OAuthRequest request = new OAuthRequest(Verb.GET, "https://" + API_HOST + path); 
    return request; 
} 

/** 
* Sends an {@link OAuthRequest} and returns the {@link Response} body. 
* 
* @param request {@link OAuthRequest} corresponding to the API request 
* @return <tt>String</tt> body of API response 
*/ 
private String sendRequestAndGetResponse(OAuthRequest request) { 
    System.out.println("Querying " + request.getCompleteUrl() + " ..."); 
    this.service.signRequest(this.accessToken, request); 
    Response response = request.send(); 
    return response.getBody(); 
} 

/** 
* Queries the Search API based on the command line arguments and takes the first result to query 
* the Business API. 
* 
* @param yelpApiCli <tt>YelpAPICLI</tt> command line arguments 
*/ 
private String queryAPI(YelpAPICLI yelpApiCli) { 
    String searchResponseJSON = 
      searchForBusinessesByLocation(yelpApiCli.term, yelpApiCli.location); 

    JSONParser parser = new JSONParser(); 
    JSONObject response = null; 
    try { 
     response = (JSONObject) parser.parse(searchResponseJSON); 
    } catch (ParseException pe) { 
     System.out.println("Error: could not parse JSON response:"); 
     System.out.println(searchResponseJSON); 
     System.exit(1); 
    } 

    JSONArray businesses = (JSONArray) response.get("businesses"); 
    JSONObject firstBusiness = (JSONObject) businesses.get(0); 
    String firstBusinessID = firstBusiness.get("id").toString(); 
    System.out.println(String.format(
      "%s businesses found, querying business info for the top result \"%s\" ...", 
      businesses.size(), firstBusinessID)); 

    // Select the first business and display business details 
    String businessResponseJSON = searchByBusinessId(firstBusinessID.toString()); 
    System.out.println(String.format("Result for business \"%s\" found:", firstBusinessID)); 
    System.out.println(businessResponseJSON); 

    return businessResponseJSON; 
} 

/** 
* Command-line interface for the sample Yelp API runner. 
*/ 
private static class YelpAPICLI { 
    @Parameter(names = {"-q", "--term"}, description = "Search Query Term") 
    public String term = DEFAULT_TERM; 

    @Parameter(names = {"-l", "--location"}, description = "Location to be Queried") 
    public String location = DEFAULT_LOCATION; 
} 

@Override 
public void start() throws Exception { 
    // Note! in real-life you wouldn't often set trust all to true as it could leave you open to man in the middle attacks. 
    this.service = 
      new ServiceBuilder().provider(TwoStepOAuth.class).apiKey(CONSUMER_KEY) 
        .apiSecret(CONSUMER_SECRET).build(); 
    this.accessToken = new Token(TOKEN, TOKEN_SECRET); 
    YelpAPICLI yelpApiCli = new YelpAPICLI(); 
    new JCommander(yelpApiCli); 


    String response = queryAPI(yelpApiCli); 
    vertx.eventBus().send("api", response); 
} 

}

我2個問題運行英寸

的第一個問題是Yelp的客戶花費太長的時間來處理該請求,並且它阻止與此警告主線程:

Jan 04, 2016 1:34:30 AM io.vertx.core.impl.BlockedThreadChecker 
WARNING: Thread Thread[vert.x-eventloop-thread-4,5,main] has been blocked for 3151 ms, time limit is 2000 

的第二個問題是,後精加工過程的第一個請求,爲例如,我第一次去我的本地主機:8080/API/V1 /業務/所有的請求返回成功地,但再下一次擊中了URL,它拋出這樣一個異常:

Jan 04, 2016 1:34:30 AM io.vertx.core.eventbus.impl.HandlerRegistration 
SEVERE: Failed to handleMessage 
java.lang.IllegalStateException: Response has already been written 

如何我可以解決這兩個問題嗎?

回答

1

問題是,您正在完成所有工作 - 啓動Yelp Verticle,並在事件總線上註冊使用者 - 請求。這不可能是你想要的。

所以,我認爲正在發生的事情:

  1. 您對您的休息API的請求。
  2. 執行getAllBusinesses()方法。
  3. YelpClientVerticle已啓動。
  4. 處理程序在api端點上註冊,該端點將寫入響應。
  5. 您的YelpClientVerticle做了很多阻塞工作 - 這就是爲什麼你得到BlockedThreadChecker警告。
  6. 最後,Yelp請求返回並通過事件總線發送一條消息,然後寫入響應。

  7. 你讓另一個請求

  8. GOTO 2

你的問題是,在每一次請求你開始另一個YelpClientVerticle,並註冊其他處理監聽的相同EventBus端點地址。

多個處理程序在同一個EventBus address上偵聽是完全可以接受的。發生這種情況時,Vert.xRoundRobin方式選擇一個處理程序。

我猜測,在第二個請求Vertx是選擇第一處理,並試圖寫你已經寫入第一個請求的響應。因此錯誤。

我會嘗試將YelpClientVerticle的部署移動到您的RestVerticle的啓動 - 那麼您將只有一個實例。

您可能希望切換髮件人/消費者,因此您將send消息發送給YelpClientVerticle,然後該消息以回覆作爲響應。

您也可能想要閱讀有關Running Blocking Code的文檔,因爲您的Yelp客戶端看起來像是阻塞了。

希望這有助於

0

這個答案是特定的例外「的反應已經被寫入」。 有人可能會在我們的代碼中使用「routingContext.next()」和「routingContext.response.end()」。如果沒有處理程序可用於路由,則vert.x將拋出上述異常。

相關問題