2016-01-03 285 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将抛出上述异常。

相关问题