Fixes #1439 - Verify if we need to handle proxy settings in com.google.cloud.tools.eclipse.projectselector.GoogleApiFactory#1513
Conversation
chanseokoh
left a comment
There was a problem hiding this comment.
Just some quick comments.
| @Component | ||
| public class GoogleApiFactory implements IGoogleApiFactory { | ||
|
|
||
| private static final String GOOGLEAPIS_URL = "https://appengine.googleapis.com"; |
There was a problem hiding this comment.
It doesn't feel right that this one contains appengine.
| import com.google.api.client.http.javanet.NetHttpTransport; | ||
| import com.google.api.client.json.JsonFactory; | ||
| import com.google.api.client.json.jackson.JacksonFactory; | ||
| import com.google.api.client.repackaged.com.google.common.base.Preconditions; |
There was a problem hiding this comment.
This is from a wrong dependency.
| Preconditions.checkNotNull(proxyService, "proxyService is null"); | ||
| jsonFactory = new JacksonFactory(); | ||
| buildTransport(); | ||
| proxyService.addProxyChangeListener(new IProxyChangeListener() { |
There was a problem hiding this comment.
I'm concerned about thread-safety here. It's not a UI widget, so I think this can be executed in any thread.
elharo
left a comment
There was a problem hiding this comment.
Looks good after Chanseok's comments are addressed
Codecov Report
@@ Coverage Diff @@
## master #1513 +/- ##
============================================
- Coverage 70.3% 70.28% -0.03%
- Complexity 1299 1300 +1
============================================
Files 232 232
Lines 8935 8942 +7
Branches 762 761 -1
============================================
+ Hits 6282 6285 +3
- Misses 2335 2339 +4
Partials 318 318
Continue to review full report at Codecov.
|
| buildCloudResourceManagerTransport(); | ||
| } catch (URISyntaxException ex) { | ||
| logger.log(Level.SEVERE, "Could not create transport using the proxy settings", ex); | ||
| transports = null; |
There was a problem hiding this comment.
I think this will cause NPE in many places onward. Even if you initialize it with an empty map, it will cause trouble in places where you do transports.get(...). Probably it's better to leave transport as-is? At least it will keep previous, working transports.
There was a problem hiding this comment.
Speaking of which, should we be prepared for when transports.get(...) returns null too?
| CloudResourceManager resourceManager = | ||
| new CloudResourceManager.Builder(transports.get(googleApiUrls.cloudResourceManagerUrl()), | ||
| jsonFactory, credential) | ||
| .setApplicationName(CloudToolsInfo.USER_AGENT).build(); |
| Appengine appengine = | ||
| new Appengine.Builder(transports.get(googleApiUrls.appEngineAdminUrl()), jsonFactory, | ||
| credential) | ||
| .setApplicationName(CloudToolsInfo.USER_AGENT).build(); |
| private static final Logger logger = Logger.getLogger(GoogleApiFactory.class.getName()); | ||
|
|
||
| private JsonFactory jsonFactory; | ||
| private Map<String, HttpTransport> transports = new HashMap<>(); |
There was a problem hiding this comment.
Are these HttpTransports expensive? Do we really want them living forever? Would we be better off making this a Guava Cache instead?
There was a problem hiding this comment.
The reason to keep them around is because of the Javadoc of NetHttpTransport:
Implementation is thread-safe. For maximum efficiency, applications should use a single globally-shared instance of the HTTP transport.
There was a problem hiding this comment.
It's your call, but I'd prefer to have them GC'd when they're unused. Guava Caches are thread-safe, so we can probably get rid of synchronization here, and the CacheLoader will simplify some of the logic here. And using weakValues() means the instances will stick around as long as they're being used.
|
|
||
| IProxyData[] proxyData = proxyService.select(new URI(url)); | ||
| for (final IProxyData iProxyData : proxyData) { | ||
| if (IProxyData.HTTPS_PROXY_TYPE.equals(iProxyData.getType())) { |
There was a problem hiding this comment.
Not supporting SOCKS proxies? Hmm, we should log something if the IProxyService returned some proxy data (i.e., we should use a proxy) and we're not supporting it — otherwise we silently fail.
| private Proxy createProxy(String url) throws URISyntaxException { | ||
| Preconditions.checkNotNull(proxyService, "proxyService is null"); | ||
| Preconditions.checkNotNull(googleApiUrls, "googleApiUrl is null"); | ||
|
|
There was a problem hiding this comment.
Since none of the Google APIs support HTTP anymore (right?) then we could assert here that the URL does not start with 'http://', so we should never see a proxy-data of type HTTP.
| Preconditions.checkNotNull(proxyService, "proxyService is null"); | ||
| jsonFactory = new JacksonFactory(); | ||
| buildTransports(); | ||
| proxyService.addProxyChangeListener(new IProxyChangeListener() { |
There was a problem hiding this comment.
I think this would be better if we made this service optionally dependent on the IProxyService (i.e., ReferenceCardinality.OPTIONAL: 0..1). If there is no proxy service, we just treat everything like DIRECT. (I believe the IProxyService is actually registered programmatically in org.eclipse.core.net's activator and so if that hasn't been activated — which is unlikely, but possible — then our service won't be available.)
…e SOCKS proxy, error on http schema
| case IProxyData.SOCKS_PROXY_TYPE: | ||
| return new Proxy(Type.SOCKS, new InetSocketAddress(iProxyData.getHost(), | ||
| iProxyData.getPort())); | ||
| default: |
There was a problem hiding this comment.
This code isn't handling a proxy directive, and we should at least log something.
There was a problem hiding this comment.
In fact, in the case of the unhandled, we should continue looping through any other proxyData items (e.g., IProxyService returns { HTTP_PROXY_TYPE, HTTPS_PROXY_TYPE } — we don't do the first, but we will do the second). The default case should happen outside of the for loop.
| } | ||
|
|
||
| IProxyData[] proxyData = proxyService.select(new URI(url)); | ||
| for (final IProxyData iProxyData : proxyData) { |
There was a problem hiding this comment.
plural: proxyDatum? Can we come up with a better name than iProxyData?
There was a problem hiding this comment.
I think @briandealwis was suggesting iProxyData --> proxyDatum. (Technically Datum is the singular form, but I guess nowadays people just use data for anything.)
There was a problem hiding this comment.
I was referring to iProxyData. And oops re: datum being singular!
| plugin.properties,\ | ||
| lib/ | ||
| lib/,\ | ||
| OSGI-INF/com.google.cloud.tools.eclipse.googleapis.internal.GoogleApiFactory.xml |
There was a problem hiding this comment.
Might just want to make this OSGI-INF/ rather than the specific file.
| Preconditions.checkNotNull(googleApiUrls, "googleApiUrl is null"); | ||
| Preconditions.checkArgument(!url.startsWith("http://"), "http is not supported schema"); | ||
|
|
||
| if (proxyService == null) { |
There was a problem hiding this comment.
I'm sorry for suggesting you make this dynamic. You should operate on a copy of the proxyService reference as it may be unbound before the call to select() below.
| @Reference(policy=ReferencePolicy.DYNAMIC, cardinality=ReferenceCardinality.OPTIONAL) | ||
| public void setProxyService(IProxyService proxyService) { | ||
| this.proxyService = proxyService; | ||
| this.proxyService.addProxyChangeListener(proxyChangeListener); |
There was a problem hiding this comment.
I'm pretty sure OSGi ensures you won't be unbound while being bound, so this field access is safe.
|
@briandealwis @elharo @chanseokoh PTAL |
|
|
||
| package com.google.cloud.tools.eclipse.googleapis; | ||
|
|
||
| public class GoogleApiException extends Exception { |
| this.proxyService = proxyService; | ||
| this.proxyService.addProxyChangeListener(proxyChangeListener); | ||
| buildTransports(); | ||
| if (proxyFactory != null) { |
There was a problem hiding this comment.
Is proxyFactory ever null? Would it make sense to make it final and set in the constructor?
chanseokoh
left a comment
There was a problem hiding this comment.
Some quick readability suggestions.
| } | ||
|
|
||
| private void connectReadAndDisconnect(HttpURLConnection connection) throws IOException { | ||
| connection.connect(); |
There was a problem hiding this comment.
I think it's concise and also better to just check
assertThat(connection.getResponseCode(), is(200));and remove the try block altogether (while leaving the finally block.)
There was a problem hiding this comment.
My intent was to check the response and fail if we accidentally connected to an existing site. I've changed the code to make this more explicit.
| }; | ||
|
|
||
| public GoogleApiFactory() { | ||
| proxyFactory = new ProxyFactory(); |
There was a problem hiding this comment.
Personally, I prefer this(new ProxyFactory()). This way, it's easy to tell if there are difference between the two constructors, and if so, how they differ.
|
|
||
| class TransportCacheLoader extends CacheLoader<GoogleApiUrl, HttpTransport> { | ||
|
|
||
| private ProxyFactory proxyFactory; |
| // used to indicate a missing application | ||
| return AppEngine.withId(application.getId()); | ||
| } catch (IOException ex) { | ||
| } catch (IOException | GoogleApiException ex) { |
There was a problem hiding this comment.
Now we're at it, I think it's better to
} catch (GoogleJsonResponseException ex) {
...
} catch (IOException | GoogleApiException ex) {
...
}and remove the if (ex instanceof GoogleJsonResponseException) {.
| APPENGINE_ADMIN_API("https://appengine.googleapis.com"), | ||
| CLOUDRESOURCE_MANAGER_API("https://cloudresourcemanager.googleapis.com"); | ||
|
|
||
| private String url; |
|
|
||
| public class GoogleApiFactoryTest { | ||
| /** | ||
| * |
|
|
||
| @Activate | ||
| public void init() { | ||
| jsonFactory = new JacksonFactory(); |
There was a problem hiding this comment.
Any reason you're not initializing this and making it final? You set mocks in the tests, but don't seem to do anything with the mocks.
| */ | ||
| class TimeoutAwareConnectionFactory extends DefaultConnectionFactory { | ||
|
|
||
| private static final int DEFAULT_TIMEOUT_MS = 1000; |
There was a problem hiding this comment.
I wonder if this should be a tuneable parameter.
There was a problem hiding this comment.
We can change it when it becomes necessary.
I've turned
GoogleApiFactoryinto a service.A proxy settings change listener is used to ensure we respect those changes.
A test for proxy setting changes is still pending, but the rest is ready for review.