PageRenderTime 39ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/host-common/src/main/java/com/atlassian/plugin/remotable/host/common/service/http/DefaultHostHttpClient.java

https://bitbucket.org/rodogu/remotable-plugins
Java | 193 lines | 164 code | 23 blank | 6 comment | 6 complexity | 5af6380ff810498ed4374a35de77cab8 MD5 | raw file
  1. package com.atlassian.plugin.remotable.host.common.service.http;
  2. import com.atlassian.fugue.Option;
  3. import com.atlassian.fugue.Pair;
  4. import com.atlassian.httpclient.api.ForwardingHttpClient;
  5. import com.atlassian.httpclient.api.HttpClient;
  6. import com.atlassian.httpclient.api.Request;
  7. import com.atlassian.httpclient.api.factory.HttpClientFactory;
  8. import com.atlassian.httpclient.api.factory.HttpClientOptions;
  9. import com.atlassian.httpclient.spi.ThreadLocalContextManager;
  10. import com.atlassian.plugin.remotable.api.service.RequestContext;
  11. import com.atlassian.plugin.remotable.api.service.SignedRequestHandler;
  12. import com.atlassian.plugin.remotable.api.service.http.HostHttpClient;
  13. import com.atlassian.plugin.remotable.host.common.util.ServicePromise;
  14. import com.atlassian.util.concurrent.Effect;
  15. import com.atlassian.util.concurrent.Promise;
  16. import com.atlassian.util.concurrent.Promises;
  17. import com.google.common.base.Function;
  18. import org.osgi.framework.BundleContext;
  19. import org.osgi.util.tracker.ServiceTracker;
  20. import javax.annotation.Nullable;
  21. import java.io.UnsupportedEncodingException;
  22. import java.lang.reflect.InvocationHandler;
  23. import java.lang.reflect.InvocationTargetException;
  24. import java.lang.reflect.Method;
  25. import java.lang.reflect.Proxy;
  26. import java.net.URI;
  27. import java.net.URLEncoder;
  28. import java.util.List;
  29. import java.util.concurrent.Callable;
  30. import java.util.concurrent.TimeUnit;
  31. import static com.atlassian.fugue.Option.option;
  32. import static com.atlassian.fugue.Pair.*;
  33. import static com.atlassian.plugin.remotable.host.common.util.ServicePromise.promiseProxy;
  34. import static com.google.common.base.Preconditions.*;
  35. public final class DefaultHostHttpClient extends ForwardingHttpClient implements HostHttpClient
  36. {
  37. private final HttpClient httpClient;
  38. private final DefaultRequestContext requestContext;
  39. public DefaultHostHttpClient(Promise<HttpClientFactory> httpClientFactoryPromise, final DefaultRequestContext requestContext, final SignedRequestHandler signedRequestHandler, final ThreadLocalContextManager threadLocalContextManager)
  40. {
  41. this.requestContext = checkNotNull(requestContext);
  42. this.httpClient = promiseProxy(httpClientFactoryPromise.map(new Function<HttpClientFactory, HttpClient>()
  43. {
  44. @Override
  45. public HttpClient apply(HttpClientFactory httpClientFactory)
  46. {
  47. return httpClientFactory.create(getHttpOptions(requestContext, checkNotNull(signedRequestHandler)), new DefaultHostHttpClientThreadLocalContextManager(threadLocalContextManager));
  48. }
  49. }), HttpClient.class);
  50. }
  51. @Override
  52. protected HttpClient delegate()
  53. {
  54. return httpClient;
  55. }
  56. private HttpClientOptions getHttpOptions(final DefaultRequestContext requestContext, SignedRequestHandler signedRequestHandler)
  57. {
  58. HttpClientOptions options = new HttpClientOptions();
  59. options.setThreadPrefix("hostclient");
  60. options.setRequestPreparer(new HostHttpClientRequestPreparer(requestContext, signedRequestHandler));
  61. return options;
  62. }
  63. @Override
  64. public <T> T callAs(String clientKey, String userId, Callable<T> callable)
  65. {
  66. final String oldClientKey = requestContext.getClientKey();
  67. final String oldUserId = requestContext.getUserId();
  68. try
  69. {
  70. requestContext.setClientKey(clientKey);
  71. requestContext.setUserId(userId);
  72. return callable.call();
  73. }
  74. catch (Exception e)
  75. {
  76. if (e instanceof RuntimeException)
  77. {
  78. throw (RuntimeException) e;
  79. }
  80. else
  81. {
  82. throw new RuntimeException(e);
  83. }
  84. }
  85. finally
  86. {
  87. requestContext.setClientKey(oldClientKey);
  88. requestContext.setUserId(oldUserId);
  89. }
  90. }
  91. private static final class HostHttpClientRequestPreparer implements Effect<Request>
  92. {
  93. private final RequestContext requestContext;
  94. private final SignedRequestHandler signedRequestHandler;
  95. private HostHttpClientRequestPreparer(RequestContext requestContext, SignedRequestHandler signedRequestHandler)
  96. {
  97. this.requestContext = checkNotNull(requestContext);
  98. this.signedRequestHandler = checkNotNull(signedRequestHandler);
  99. }
  100. @Override
  101. public void apply(Request request)
  102. {
  103. // make sure this is a request for a relative url
  104. if (request.getUri().toString().matches("^[\\w]+:.*"))
  105. {
  106. throw new IllegalStateException("Absolute request URIs are not supported for host requests");
  107. }
  108. // get the current oauth client key and die if it's not available
  109. String clientKey = requestContext.getClientKey();
  110. if (clientKey == null)
  111. {
  112. throw new IllegalStateException("Unable to execute host http request without client key");
  113. }
  114. // lookup the host base url from the client key
  115. final String baseUrl = signedRequestHandler.getHostBaseUrl(clientKey);
  116. // build initial absolute request url from the base and the request uri
  117. final String origUriStr = baseUrl + request.getUri();
  118. final StringBuilder uriBuf = new StringBuilder(origUriStr);
  119. // append the user id to the uri if available
  120. final String userId = requestContext.getUserId();
  121. if (userId != null)
  122. {
  123. try
  124. {
  125. uriBuf
  126. .append(uriBuf.indexOf("?") > 0 ? '&' : '?')
  127. .append("user_id")
  128. .append('=')
  129. .append(URLEncoder.encode(userId, "UTF-8"));
  130. }
  131. catch (UnsupportedEncodingException e)
  132. {
  133. throw new RuntimeException(e);
  134. }
  135. }
  136. request.setUri(URI.create(uriBuf.toString()));
  137. final Request.Method method = request.getMethod();
  138. final String authHeader = signedRequestHandler.getAuthorizationHeaderValue(URI.create(origUriStr), method.name(), userId);
  139. request.setHeader("Authorization", authHeader)
  140. // capture request properties for analytics
  141. .setAttribute("purpose", "host-request")
  142. .setAttribute("clientKey", clientKey);
  143. }
  144. }
  145. private static final class DefaultHostHttpClientThreadLocalContextManager<C> implements ThreadLocalContextManager<Pair<DefaultRequestContext.RequestData, Option<C>>>
  146. {
  147. private final ThreadLocalContextManager<C> threadLocalContextManager;
  148. private DefaultHostHttpClientThreadLocalContextManager(ThreadLocalContextManager<C> threadLocalContextManager)
  149. {
  150. this.threadLocalContextManager = checkNotNull(threadLocalContextManager);
  151. }
  152. @Override
  153. public Pair<DefaultRequestContext.RequestData, Option<C>> getThreadLocalContext()
  154. {
  155. return pair(DefaultRequestContext.getRequestData(), option(threadLocalContextManager.getThreadLocalContext()));
  156. }
  157. @Override
  158. public void setThreadLocalContext(Pair<DefaultRequestContext.RequestData, Option<C>> context)
  159. {
  160. DefaultRequestContext.setRequestData(context.left());
  161. threadLocalContextManager.setThreadLocalContext(context.right().getOrNull());
  162. }
  163. @Override
  164. public void resetThreadLocalContext()
  165. {
  166. DefaultRequestContext.clear();
  167. threadLocalContextManager.resetThreadLocalContext();
  168. }
  169. }
  170. }