查询Google Maps API时本地托管的SparkJava代理服务器停顿

问题描述

我正在尝试使用SparkJava编写代理服务器,以查询来自客户端的给定参数(即位置数据,交通模型偏好,出发时间等)的Google Maps Directions API,并返回各种路由详细信息,例如距离,持续时间和持续时间。

当服务器尝试代表客户端向API发送请求时,服务器停止运行。我在整个代码中放置了打印语句,以确认该挂起是由于API查询引起的。我尝试使用4567方法使用不同的端口,即443808080port(),但是问题仍然存在。我确信进行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>