blob: 8fdc86c3bb7c946b2b0a7b306d0f5dea17656a05 [file] [log] [blame]
ejona07d3f6a2014-05-14 11:26:57 -07001package com.google.net.stubby.http;
2
3import com.google.common.io.ByteBuffers;
4import com.google.net.stubby.AbstractRequest;
lryane4bd1c72014-09-08 14:03:35 -07005import com.google.net.stubby.Metadata;
ejona07d3f6a2014-05-14 11:26:57 -07006import com.google.net.stubby.Operation;
7import com.google.net.stubby.Response;
8import com.google.net.stubby.Session;
9import com.google.net.stubby.Status;
10import com.google.net.stubby.transport.Framer;
11import com.google.net.stubby.transport.MessageFramer;
ejona07d3f6a2014-05-14 11:26:57 -070012
13import java.io.DataOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.net.HttpURLConnection;
17import java.net.URI;
18import java.nio.ByteBuffer;
19
20/**
21 * Implementation of {@link Session} using {@link HttpURLConnection} for clients. Services
22 * are dispatched relative to a base URI.
23 */
24public class UrlConnectionClientSession implements Session {
25
26 private final URI base;
27
28 public UrlConnectionClientSession(URI base) {
29 this.base = base;
30 }
31
32 @Override
lryane4bd1c72014-09-08 14:03:35 -070033 public Request startRequest(String operationName, Metadata.Headers headers,
lryan34650402014-07-14 13:39:39 -070034 Response.ResponseBuilder responseBuilder) {
lryane4bd1c72014-09-08 14:03:35 -070035 return new Request(base.resolve(operationName), headers,
36 responseBuilder.build());
ejona07d3f6a2014-05-14 11:26:57 -070037 }
38
39 private class Request extends AbstractRequest implements Framer.Sink {
40
41 private final HttpURLConnection connection;
42 private final DataOutputStream outputStream;
43 private final MessageFramer framer;
44
lryane4bd1c72014-09-08 14:03:35 -070045 private Request(URI uri, Metadata.Headers headers, Response response) {
ejona07d3f6a2014-05-14 11:26:57 -070046 super(response);
47 try {
48 connection = (HttpURLConnection) uri.toURL().openConnection();
49 connection.setDoOutput(true);
50 connection.setDoInput(true);
51 connection.setRequestMethod("POST");
52 connection.setRequestProperty("Content-Type", "application/protorpc");
lryane4bd1c72014-09-08 14:03:35 -070053 String[] serialized = headers.serializeAscii();
54 for (int i = 0; i < serialized.length; i++) {
55 connection.setRequestProperty(
56 serialized[i],
57 serialized[++i]);
lryan34650402014-07-14 13:39:39 -070058 }
ejona07d3f6a2014-05-14 11:26:57 -070059 outputStream = new DataOutputStream(connection.getOutputStream());
60 } catch (IOException t) {
61 throw new RuntimeException(t);
62 }
63 // No compression when framing over HTTP for the moment
64 framer = new MessageFramer(4096);
65 framer.setAllowCompression(false);
66 }
67
68 @Override
ejona07d3f6a2014-05-14 11:26:57 -070069 public Operation addPayload(InputStream payload, Phase nextPhase) {
70 super.addPayload(payload, nextPhase);
71 framer.writePayload(payload, getPhase() == Phase.CLOSED, this);
72 return this;
73 }
74
75 @Override
lryan2ce84462014-06-02 14:43:36 -070076 public Operation close(Status status) {
77 // TODO(user): This is broken but necessary to get test passing with the introduction
78 // of Channel as now for most calls the close() call is decoupled from the last call to
79 // addPayload. The real fix is to remove 'nextPhase' from the Operation interface and
80 // clean up Framer. For a follow up CL.
81 boolean alreadyClosed = getPhase() == Phase.CLOSED;
82 super.close(status);
83 if (!alreadyClosed) {
84 framer.writeStatus(status, true, this);
85 }
86 return this;
87 }
88
89 @Override
ejona07d3f6a2014-05-14 11:26:57 -070090 public void deliverFrame(ByteBuffer frame, boolean endOfMessage) {
91 boolean closed = getPhase() == Phase.CLOSED;
ejona07d3f6a2014-05-14 11:26:57 -070092 try {
93 ByteBuffers.asByteSource(frame).copyTo(outputStream);
94 if (closed && endOfMessage) {
95 connection.getOutputStream().close();
96 // The request has completed so now process the response. Must do this in the same
97 // thread as URLConnection has threading issues.
98 new HttpStreamDeframer().deframe(connection.getInputStream(), getResponse());
99 connection.getInputStream().close();
100 connection.disconnect();
101 }
102 } catch (IOException ioe) {
lryan71e4a922014-09-25 18:25:54 -0700103 close(Status.INTERNAL.withCause(ioe));
ejona07d3f6a2014-05-14 11:26:57 -0700104 }
105 }
106 }
107}