在查询Google Maps API时,本地托管的SparkJava代理服务器出现停顿。

huangapple go评论77阅读模式
英文:

Locally Hosted SparkJava Proxy Server Stalls when Querying Google Maps API

问题

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();
}
public final class DirectionsUtility{
    // ... (other code)

    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
    
    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;
    }
}
public class App 
{
    // ... (other code)

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

<details>
<summary>英文:</summary>

I am trying to write a proxy server with SparkJava that queries the Google Maps Directions API given parameters (i.e. location data, traffic model preference, departure time, etc...) from a client and returns various routing details such as distance, duration, and duration.  

The server stalls when it tries to send a request to the API on behalf of the client. I placed print statements throughout the code to confirm that the hang was due to the API query. I have tried using different ports namely: `4567`, `443`, `80`, and `8080` by using `port()` method but the problem persists. I am sure the server-side code conducting the API query is not the issue; everything works fine (proper route information is generated i.e. `DirectionsApiRequest.await()` returns properly) when I cut the client out, disable the endpoints, and run everything manually from the main method on the (deactivated) server&#39;s side.  

Does anyone know why this could be happening?
(I use maven for dependency management)

The following shows the client trying to get the distance of the default route and the aforementioned error:

Server-side code:

`Main class`
~~~java
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&lt;LatLng&gt; locationsDatabase = new ArrayList&lt;LatLng&gt;();
    private static DirectionsRoute defaultRoute = null;

    public static void main( String[] args ) throws ApiException, InterruptedException, IOException
    {
        // client posts location data
        post(&quot;routingEngine/sendLocations&quot;, (request,response) -&gt; {
            response.type(&quot;application/json&quot;);
            ArrayList&lt;LatLng&gt; locations = new Gson().fromJson(request.body(),new TypeToken&lt;ArrayList&lt;LatLng&gt;&gt;(){}.getType());
            locationsDatabase = locations;
            return &quot;OK&quot;;
        });

		// before any default route queries, the default route must be generated
        before(&quot;routingEngine/getDefaultRoute/*&quot;,(request,response) -&gt;{
            RequestParameters requestParameters = new Gson().fromJson(request.body(),(java.lang.reflect.Type)RequestParameters.class);
            defaultRoute = DirectionsUtility.getDefaultRoute(locationsDatabase,requestParameters);
        });

        // client gets default route distance
        get(&quot;routingEngine/getDefaultRoute/distance&quot;, (request,response) -&gt;{
            response.type(&quot;application/json&quot;);
            return new Gson().toJson(new Gson().toJson(DirectionsUtility.getDefaultRouteDistance(defaultRoute)));
        });

        DirectionsUtility.context.shutdown();
    }
}
~~~

`DirectionsUtility` is the class responsible for consulting with Google Maps&#39; API:
~~~java
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 = &quot;YOUR PERSONAL API KEY&quot;;
    
    /**
    *  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&lt;LatLng&gt; 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 &lt; 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(&quot;firing off API query...&quot;);
            directionsResult = directionsRequest.await();
            // testing - notification that api query was successful
            System.out.println(&quot;API query successful&quot;);

        }
        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(&quot;Computing distance...&quot;);
        // each route has only 1 leg since all the waypoints are non-stopover points
        return defaultRoute.legs[0].distance;
    }

 }
~~~

Here is the client-side code:

~~~java
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(&quot;application/json; charset=utf-8&quot;);

    public static void main( String[] args ) throws IOException
    {
        // post location data

    	// get locations from database
        ArrayList&lt;LatLng&gt; locations = new ArrayList&lt;LatLng&gt;();
        // 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(&quot;http://localhost:4567/routingEngine/sendLocations&quot;)
        .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) ? &quot;true&quot; : &quot;false&quot;;
        TrafficModel trafficModel = TrafficModel.BEST_GUESS;
        // restrictions
        RouteRestriction[] restrictions = {RouteRestriction.TOLLS,RouteRestriction.FERRIES};
        String region = &quot;us&quot;; // USA
        String language = &quot;en-EN&quot;;
        RequestParameters requestParameters = new RequestParameters(unit,departureTime,true,trafficModel,restrictions,region,language);
        
        // build url
        HttpUrl url = new HttpUrl.Builder()
        .scheme(&quot;http&quot;)
        .host(&quot;127.0.0.1&quot;)
        .port(4567)
        .addPathSegment(&quot;routingEngine&quot;)
        .addPathSegment(&quot;getDefaultRoute&quot;)
        .addPathSegment(&quot;distance&quot;)
        .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(&quot;Default Route Distance: &quot; + defaultRouteDistance);

    }
}
~~~
Both classes use the following class RequestParameters to package all the request parameters together (i.e. unit, departure time, region, language etc...) just for convenience
~~~java
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;		
	}	

}
~~~
Hopefully this provides the information necessary to investigate the problem.

</details>


# 答案1
**得分**: 1

在服务器端的 `App` 类中,`main` 方法的最后一行代码如下

```java
DirectionsUtility.context.shutdown();

这会有效地关闭地图服务 API 使用的 ExecutorService(位于其 RateLimitExecutorService 内部),负责实际执行发送到 Google 的请求。因此,您的请求被加入队列,但实际上并未被执行。

此外,在 DirectionsUtility 类中,与其使用 System.out.println(e)(内部)相比,最好使用 e.printStackTrace() 这样的写法,这样您将能够获取完整的错误信息以及其堆栈跟踪。

英文:

In the server-side App class, the last line of the main method reads

DirectionsUtility.context.shutdown();

This effectively shuts down the ExecutorService that the Maps Services API uses (inside its RateLimitExecutorService) and that is responsible for actually executing requests to Google. So your request is enqueued, but never actually executed.

Also, instead of doing System.out.println(e) (inside the DirectionsUtility class) it may be better do something like e.printStacktrace() so you'll have access to the whole error + it's stack.

huangapple
  • 本文由 发表于 2020年8月28日 04:20:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/63623654.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定