本地托管的 SparkJava 代理服务器在查询 Google 映射 API 时停止

Locally Hosted SparkJava Proxy Server Stalls when Querying Google Maps API

我正在尝试使用 SparkJava 编写一个代理服务器来查询 Google 地图方向 API 给定参数(即位置数据、交通模型偏好、出发时间等...)一个客户端和 returns 各种路由详细信息,例如距离、持续时间和持续时间。

服务器在尝试代表客户端向 API 发送请求时停止。我在整个代码中放置了 print 语句,以确认挂起是由于 API 查询引起的。我尝试使用不同的端口,即:4567443808080,方法是 port(),但问题仍然存在。我确信执行 API 查询的服务器端代码不是问题;一切正常(正确的路由信息​​生成,即 DirectionsApiRequest.await() returns 正确)当我切断客户端,禁用端点,并且 运行 从主方法手动从(停用)服务器端。

有谁知道为什么会这样? (我用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/*",(request,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", (request,response) ->{
            response.type("application/json");
            return new Gson().toJson(new Gson().toJson(DirectionsUtility.getDefaultRouteDistance(defaultRoute)));
        });

        DirectionsUtility.context.shutdown();
    }
}

DirectionsUtility 是 class 负责咨询 Google 地图的 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, InterruptedException, 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)
    .writeTimeout(700, TimeUnit.SECONDS)
    .readTimeout(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);

    }
}

两个class都是为了方便使用下面的classRequestParameters将所有请求参数打包在一起(即单位,出发时间,地区,语言等...)

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;       
    }   

}

希望这提供了调查问题所需的信息。

在server-sideAppclass中,main方法的最后一行是

DirectionsUtility.context.shutdown();

这有效地关闭了地图服务 API 使用的 ExecutorService(在其 RateLimitExecutorService 内),它负责实际执行对 Google 的请求。所以您的请求已入队,但从未真正执行过。

此外,与其执行 System.out.println(e)(在 DirectionsUtility class 内),不如执行 e.printStacktrace() 这样的操作,这样您就可以访问整个错误 + 它是堆栈。