问题描述
我正在尝试使用SparkJava编写代理服务器,以查询来自客户端的给定参数(即位置数据,交通模型偏好,出发时间等)的Google Maps Directions API,并返回各种路由详细信息,例如距离,持续时间和持续时间。
当服务器尝试代表客户端向API发送请求时,服务器停止运行。我在整个代码中放置了打印语句,以确认该挂起是由于API查询引起的。我尝试使用4567
方法使用不同的端口,即443
,80
,8080
和port()
,但是问题仍然存在。我确信进行API查询的服务器端代码不是问题;当我切断客户端,禁用端点并从(已停用的)服务器端的main方法手动运行所有操作时,一切工作正常(生成了正确的路由信息,即DirectionsApiRequest.await()
正常返回)。
有人知道为什么会这样吗? (我使用maven进行依赖管理)
服务器端代码:
Main class
package com.mycompany.app;
//import
// data structures
import java.util.ArrayList;
// google maps
import com.google.maps.model.DirectionsRoute;
import com.google.maps.model.LatLng;
// gson
import com.google.gson.Gson;
import com.google.gson.reflect.Typetoken;
import java.lang.reflect.Type;
// static API methods
import com.mycompany.app.DirectionsUtility;
import static spark.Spark.*;
// exceptions
import com.google.maps.errors.ApiException;
import java.io.IOException;
public class App
{
private static ArrayList<LatLng> locationsDatabase = new ArrayList<LatLng>();
private static DirectionsRoute defaultRoute = null;
public static void main( String[] args ) throws ApiException,InterruptedException,IOException
{
// client posts location data
post("routingEngine/sendLocations",(request,response) -> {
response.type("application/json");
ArrayList<LatLng> locations = new Gson().fromJson(request.body(),new Typetoken<ArrayList<LatLng>>(){}.getType());
locationsDatabase = locations;
return "OK";
});
// before any default route queries,the default route must be generated
before("routingEngine/getDefaultRoute/*",response) ->{
RequestParameters requestParameters = new Gson().fromJson(request.body(),(java.lang.reflect.Type)RequestParameters.class);
defaultRoute = DirectionsUtility.getDefaultRoute(locationsDatabase,requestParameters);
});
// client gets default route distance
get("routingEngine/getDefaultRoute/distance",response) ->{
response.type("application/json");
return new Gson().toJson(new Gson().toJson(DirectionsUtility.getDefaultRoutedistance(defaultRoute)));
});
DirectionsUtility.context.shutdown();
}
}
DirectionsUtility
是负责咨询Google Maps API的类:
package com.mycompany.app;
// import
// data structures
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.HashMap;
// Google Directions API
import com.google.maps.GeoApiContext;
// request parameters
import com.google.maps.DirectionsApiRequest;
import com.google.maps.model.Unit;
import com.google.maps.model.TravelMode;
import com.google.maps.model.TrafficModel;
import com.google.maps.DirectionsApi.RouteRestriction;
import com.google.maps.model.distance;
// result parameters
import com.google.maps.model.DirectionsResult;
import com.google.maps.model.LatLng;
import com.google.maps.model.DirectionsRoute;
import com.google.maps.model.DirectionsLeg;
// exceptions
import com.google.maps.errors.ApiException;
import java.io.IOException;
// time constructs
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Call;
import okhttp3.Response;
import okhttp3.MediaType;
import okhttp3.HttpUrl;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public final class DirectionsUtility{
/**
* Private constructor to prevent instantiation.
*/
private DirectionsUtility(){}
/**
* API key.
*/
private static final String API_KEY = "YOUR PERSONAL API KEY";
/**
* Queries per second limit (50 is max).
*/
private static int QPS = 50;
/**
* Singleton that facilitates Google Geo API queries; must be shutdown() for program termination.
*/
protected static GeoApiContext context = new GeoApiContext.Builder()
.apiKey(API_KEY)
.queryRateLimit(QPS)
.build();
// TESTING
// singleton client
private static final OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(700,TimeUnit.SECONDS)
.writeTimeout(700,TimeUnit.SECONDS)
.readTimeout(700,TimeUnit.SECONDS)
.build();
/**
* Generates the route judged by the Google Api as being the most optimal. The main purpose of this method is to provide a fallback
* for the optimization engine should it ever find the Traditional processes of this server (i.e. generation of all possible routes)
* too slow for its taste. In other words,if this server delays to an excessive degree in providing the optimization engine with the
* set of all possible routes,the optimization engine can terminate those processes and instead entrust the decision to the Google
* Maps API. This method suffers from a minor caveat; the Google Maps API refuses to compute the duration in traffic for any journey
* involving multiple locations if the intermediate points separating the origin and destination are assumed to be stopover points (i.e.
* if it is assumed that the driver will stop at each point) therefore this method assumes that the driver will not stop at the intermediate
* points. This may introduce some inaccuracies into the predictions.
* (it should be noted that this server has not yet been equipped with the ability to generate all possible routes so this method is,at the
* at the moment,the only option)
*
* @param requestParameters the parameters required for a Google Maps API query; see the RequestParameters class for more information
*
* @return the default route
*/
public static DirectionsRoute getDefaultRoute(ArrayList<LatLng> locations,RequestParameters requestParameters) throws ApiException,IOException
{
LatLng origin = locations.get(0);
LatLng destination = locations.get(locations.size() - 1);
// separate waypoints
int numWaypoints = locations.size() - 2;
DirectionsApiRequest.Waypoint[] waypoints = new DirectionsApiRequest.Waypoint[numWaypoints];
for(int i = 0; i < waypoints.length; i++)
{
// ensure that each waypoint is not designated as a stopover point
waypoints[i] = new DirectionsApiRequest.Waypoint(locations.get(i + 1),false);
}
// send API query
// store API query response
DirectionsResult directionsResult = null;
try
{
// create DirectionsApiRequest object
DirectionsApiRequest directionsRequest = new DirectionsApiRequest(context);
// set request parameters
directionsRequest.units(requestParameters.getUnit());
directionsRequest.mode(TravelMode.DRIVING);
directionsRequest.trafficModel(requestParameters.getTrafficModel());
if(requestParameters.getRestrictions() != null)
{
directionsRequest.avoid(requestParameters.getRestrictions());
}
directionsRequest.region(requestParameters.getRegion());
directionsRequest.language(requestParameters.getLanguage());
directionsRequest.departureTime(requestParameters.getDepartureTime());
// always generate alternative routes
directionsRequest.alternatives(false);
directionsRequest.origin(origin);
directionsRequest.destination(destination);
directionsRequest.waypoints(waypoints);
directionsRequest.optimizeWaypoints(requestParameters.optimizeWaypoints());
// send request and store result
// testing - notification that a new api query is being sent
System.out.println("firing off API query...");
directionsResult = directionsRequest.await();
// testing - notification that api query was successful
System.out.println("API query successful");
}
catch(Exception e)
{
System.out.println(e);
}
// directionsResult.routes contains only a single,optimized route
// return the default route
return directionsResult.routes[0];
} // end method
/**
* Returns the distance of the default route.
*
* @param defaultRoute the default route
*
* @return the distance of the default route
*/
public static distance getDefaultRoutedistance(DirectionsRoute defaultRoute)
{
// testing - simple notification
System.out.println("Computing distance...");
// each route has only 1 leg since all the waypoints are non-stopover points
return defaultRoute.legs[0].distance;
}
}
这是客户端代码:
package com.mycompany.app;
import java.util.ArrayList;
import java.util.Arrays;
import com.google.maps.model.LatLng;
import com.google.maps.model.TrafficModel;
import com.google.maps.DirectionsApi.RouteRestriction;
import com.google.maps.model.TransitRoutingPreference;
import com.google.maps.model.TravelMode;
import com.google.maps.model.Unit;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonArray;
import com.google.gson.reflect.Typetoken;
import java.lang.reflect.Type;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Call;
import okhttp3.Response;
import okhttp3.MediaType;
import okhttp3.HttpUrl;
// time constructs
import java.time.LocalDateTime;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.concurrent.TimeUnit;
import com.google.maps.model.distance;
import com.google.maps.model.Duration;
import java.io.IOException;
public class App
{
// model database
private static LatLng hartford_ct = new LatLng(41.7658,-72.6734);
private static LatLng loretto_pn = new LatLng(40.5031,-78.6303);
private static LatLng chicago_il = new LatLng(41.8781,-87.6298);
private static LatLng newyork_ny = new LatLng(40.7128,-74.0060);
private static LatLng newport_ri = new LatLng(41.4901,-71.3128);
private static LatLng concord_ma = new LatLng(42.4604,-71.3489);
private static LatLng washington_dc = new LatLng(38.8951,-77.0369);
private static LatLng greensboro_nc = new LatLng(36.0726,-79.7920);
private static LatLng atlanta_ga = new LatLng(33.7490,-84.3880);
private static LatLng tampa_fl = new LatLng(27.9506,-82.4572);
// singleton client
private static final OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(700,TimeUnit.SECONDS)
.build();
private static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
public static void main( String[] args ) throws IOException
{
// post location data
// get locations from database
ArrayList<LatLng> locations = new ArrayList<LatLng>();
// origin
LatLng origin = hartford_ct;
locations.add(origin);
// waypoints
locations.add(loretto_pn);
locations.add(chicago_il);
locations.add(newyork_ny);
locations.add(newport_ri);
locations.add(concord_ma);
locations.add(washington_dc);
locations.add(greensboro_nc);
locations.add(atlanta_ga);
// destination
LatLng destination = tampa_fl;
locations.add(destination);
// serialize locations list to json
Gson gson = new GsonBuilder().create();
String locationsJson = gson.toJson(locations);
// post to routing engine
RequestBody postLocationsRequestBody = RequestBody.create(JSON,locationsJson);
Request postLocationsRequest = new Request.Builder()
.url("http://localhost:4567/routingEngine/sendLocations")
.post(postLocationsRequestBody)
.build();
Call postLocationsCall = httpClient.newCall(postLocationsRequest);
Response postLocationsResponse = postLocationsCall.execute();
// get distance of default route
// generate parameters
Unit unit = Unit.METRIC;
LocalDateTime temp = LocalDateTime.Now();
Instant departureTime= temp.atZone(ZoneOffset.UTC)
.withYear(2025)
.withMonth(8)
.withDayOfMonth(18)
.withHour(10)
.withMinute(12)
.withSecond(10)
.withNano(900)
.toInstant();
boolean optimizeWaypoints = true;
String optimizeWaypointsstring = (optimizeWaypoints == true) ? "true" : "false";
TrafficModel trafficModel = TrafficModel.BEST_GUESS;
// restrictions
RouteRestriction[] restrictions = {RouteRestriction.TOLLS,RouteRestriction.FERRIES};
String region = "us"; // USA
String language = "en-EN";
RequestParameters requestParameters = new RequestParameters(unit,departureTime,true,trafficModel,restrictions,region,language);
// build url
HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host("127.0.0.1")
.port(4567)
.addpathSegment("routingEngine")
.addpathSegment("getDefaultRoute")
.addpathSegment("distance")
.build();
// build request
Request getDefaultRoutedistanceRequest = new Request.Builder()
.url(url)
.post(RequestBody.create(JSON,gson.toJson(requestParameters)))
.build();
// send request
Call getDefaultRoutedistanceCall = httpClient.newCall(getDefaultRoutedistanceRequest);
Response getDefaultRoutedistanceResponse = getDefaultRoutedistanceCall.execute();
// store and print response
distance defaultRoutedistance = gson.fromJson(getDefaultRoutedistanceResponse.body().string(),distance.class);
System.out.println("Default Route distance: " + defaultRoutedistance);
}
}
这两个类都使用下面的RequestParameters类将所有请求参数打包在一起(即单位,出发时间,地区,语言等),只是为了方便起见
package com.mycompany.app;
import com.google.maps.model.Unit;
import java.time.Instant;
import com.google.maps.model.TrafficModel;
import com.google.maps.DirectionsApi.RouteRestriction;
public class RequestParameters
{
private Unit unit;
private Instant departureTime;
private boolean optimizeWaypoints;
private TrafficModel trafficModel;
private RouteRestriction[] restrictions;
private String region;
private String language;
public RequestParameters(Unit unit,Instant departureTime,boolean optimizeWaypoints,TrafficModel trafficModel,RouteRestriction[] restrictions,String region,String language)
{
this.unit = unit;
this.departureTime = departureTime;
this.optimizeWaypoints = optimizeWaypoints;
this.trafficModel = trafficModel;
this.restrictions = restrictions;
this.region = region;
this.language = language;
}
// getters
public Unit getUnit()
{
return this.unit;
}
public Instant getDepartureTime()
{
return this.departureTime;
}
public boolean optimizeWaypoints()
{
return this.optimizeWaypoints;
}
public TrafficModel getTrafficModel()
{
return this.trafficModel;
}
public RouteRestriction[] getRestrictions()
{
return this.restrictions;
}
public String getRegion()
{
return this.region;
}
public String getLanguage()
{
return this.language;
}
// setters
public void setTrafficModel(TrafficModel trafficModel)
{
this.trafficModel = trafficModel;
}
public void setRegion(String region)
{
this.region = region;
}
public void setLanguage(String language)
{
this.language = language;
}
}
希望这提供了调查问题所需的信息。
解决方法
在服务器端App
类中,main
方法的最后一行读取
DirectionsUtility.context.shutdown();
这有效地关闭了Maps Services API使用的ExecutorService
(在其RateLimitExecutorService
内部),它负责实际执行对Google的请求。因此,您的请求已入队,但从未真正执行过。
此外,与其做System.out.println(e)
(在DirectionsUtility
类内部),不如做类似e.printStacktrace()
的事情,这样您就可以访问整个错误+它是堆栈。 / p>