blob: 22794e4b545825a8b545d26539d808d17fa09ca1 [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;
5import com.google.net.stubby.Operation;
6import com.google.net.stubby.Response;
7import com.google.net.stubby.Session;
8import com.google.net.stubby.Status;
9import com.google.net.stubby.transport.Framer;
10import com.google.net.stubby.transport.MessageFramer;
11import com.google.net.stubby.transport.Transport;
12
13import java.io.DataOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.net.HttpURLConnection;
17import java.net.URI;
18import java.nio.ByteBuffer;
lryan34650402014-07-14 13:39:39 -070019import java.util.Map;
ejona07d3f6a2014-05-14 11:26:57 -070020
21/**
22 * Implementation of {@link Session} using {@link HttpURLConnection} for clients. Services
23 * are dispatched relative to a base URI.
24 */
25public class UrlConnectionClientSession implements Session {
26
27 private final URI base;
28
29 public UrlConnectionClientSession(URI base) {
30 this.base = base;
31 }
32
33 @Override
lryan34650402014-07-14 13:39:39 -070034 public Request startRequest(String operationName, Map<String, String> headers,
35 Response.ResponseBuilder responseBuilder) {
36 return new Request(base.resolve(operationName), headers, 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
lryan34650402014-07-14 13:39:39 -070045 private Request(URI uri, Map<String, String> 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");
lryan34650402014-07-14 13:39:39 -070053 for (Map.Entry<String, String> header : headers.entrySet()) {
54 connection.setRequestProperty(header.getKey(), header.getValue());
55 }
ejona07d3f6a2014-05-14 11:26:57 -070056 outputStream = new DataOutputStream(connection.getOutputStream());
57 } catch (IOException t) {
58 throw new RuntimeException(t);
59 }
60 // No compression when framing over HTTP for the moment
61 framer = new MessageFramer(4096);
62 framer.setAllowCompression(false);
63 }
64
65 @Override
66 public Operation addContext(String type, InputStream message, Phase nextPhase) {
67 super.addContext(type, message, nextPhase);
68 framer.writeContext(type, message, getPhase() == Phase.CLOSED, this);
69 return this;
70 }
71
72 @Override
73 public Operation addPayload(InputStream payload, Phase nextPhase) {
74 super.addPayload(payload, nextPhase);
75 framer.writePayload(payload, getPhase() == Phase.CLOSED, this);
76 return this;
77 }
78
79 @Override
lryan2ce84462014-06-02 14:43:36 -070080 public Operation close(Status status) {
81 // TODO(user): This is broken but necessary to get test passing with the introduction
82 // of Channel as now for most calls the close() call is decoupled from the last call to
83 // addPayload. The real fix is to remove 'nextPhase' from the Operation interface and
84 // clean up Framer. For a follow up CL.
85 boolean alreadyClosed = getPhase() == Phase.CLOSED;
86 super.close(status);
87 if (!alreadyClosed) {
88 framer.writeStatus(status, true, this);
89 }
90 return this;
91 }
92
93 @Override
ejona07d3f6a2014-05-14 11:26:57 -070094 public void deliverFrame(ByteBuffer frame, boolean endOfMessage) {
95 boolean closed = getPhase() == Phase.CLOSED;
ejona07d3f6a2014-05-14 11:26:57 -070096 try {
97 ByteBuffers.asByteSource(frame).copyTo(outputStream);
98 if (closed && endOfMessage) {
99 connection.getOutputStream().close();
100 // The request has completed so now process the response. Must do this in the same
101 // thread as URLConnection has threading issues.
102 new HttpStreamDeframer().deframe(connection.getInputStream(), getResponse());
103 connection.getInputStream().close();
104 connection.disconnect();
105 }
106 } catch (IOException ioe) {
107 close(new Status(Transport.Code.INTERNAL, ioe));
ejona07d3f6a2014-05-14 11:26:57 -0700108 }
109 }
110 }
111}