13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 */
24 package java.net.http;
25
26 import java.io.IOException;
27 import java.net.URI;
28 import java.nio.ByteBuffer;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.net.InetSocketAddress;
33 import java.nio.charset.StandardCharsets;
34 import java.util.function.LongConsumer;
35 import static java.nio.charset.StandardCharsets.US_ASCII;
36
37 /**
38 * A HTTP/1.1 request.
39 *
40 * send() -> Writes the request + body to the given channel, in one blocking
41 * operation.
42 */
43 class Http1Request {
44
45 final HttpRequestImpl request;
46 final HttpConnection chan;
47 // Multiple buffers are used to hold different parts of request
48 // See line 206 and below for description
49 final ByteBuffer[] buffers;
50 final HttpRequest.BodyProcessor requestProc;
51 final HttpHeadersImpl userHeaders, systemHeaders;
52 final LongConsumer flowController;
53 boolean streaming;
54 long contentLength;
55
56 Http1Request(HttpRequestImpl request, HttpConnection connection)
57 throws IOException
58 {
59 this.request = request;
60 this.chan = connection;
61 buffers = new ByteBuffer[5]; // TODO: check
62 this.requestProc = request.requestProcessor();
63 this.userHeaders = request.getUserHeaders();
64 this.systemHeaders = request.getSystemHeaders();
65 this.flowController = this::dummy;
66 }
67
68 private void logHeaders() throws IOException {
69 StringBuilder sb = new StringBuilder(256);
70 sb.append("REQUEST HEADERS:\r\n");
71 collectHeaders1(sb, request, systemHeaders);
74 }
75
76 private void dummy(long x) {
77 // not used in this class
78 }
79
80 private void collectHeaders0() throws IOException {
81 if (Log.headers()) {
82 logHeaders();
83 }
84 StringBuilder sb = new StringBuilder(256);
85 collectHeaders1(sb, request, systemHeaders);
86 collectHeaders1(sb, request, userHeaders);
87 sb.append("\r\n");
88 String headers = sb.toString();
89 buffers[1] = ByteBuffer.wrap(headers.getBytes(StandardCharsets.US_ASCII));
90 }
91
92 private void collectHeaders1(StringBuilder sb,
93 HttpRequestImpl request,
94 HttpHeadersImpl headers)
95 throws IOException
96 {
97 Map<String,List<String>> h = headers.directMap();
98 Set<Map.Entry<String,List<String>>> entries = h.entrySet();
99
100 for (Map.Entry<String,List<String>> entry : entries) {
101 String key = entry.getKey();
102 sb.append(key).append(": ");
103 List<String> values = entry.getValue();
104 int num = values.size();
105 for (String value : values) {
106 sb.append(value);
107 if (--num > 0) {
108 sb.append(',');
109 }
110 }
111 sb.append("\r\n");
112 }
113 }
114
115 private static final int BUFSIZE = 64 * 1024; // TODO: configurable?
116
117 private String getPathAndQuery(URI uri) {
118 String path = uri.getPath();
119 String query = uri.getQuery();
120 if (path == null || path.equals("")) {
121 path = "/";
122 }
123 if (query == null) {
124 query = "";
125 }
126 if (query.equals("")) {
127 return path;
128 } else {
129 return path + "?" + query;
130 }
131 }
132
133 private String authorityString(InetSocketAddress addr) {
134 return addr.getHostString() + ":" + addr.getPort();
135 }
136
137 private String requestURI() {
138 URI uri = request.uri();
139 String method = request.method();
140
141 if ((request.proxy() == null && !method.equals("CONNECT"))
142 || request.isWebSocket()) {
143 return getPathAndQuery(uri);
144 }
145 if (request.secure()) {
146 if (request.method().equals("CONNECT")) {
147 // use authority for connect itself
148 return authorityString(request.authority());
149 } else {
150 // requests over tunnel do not require full URL
151 return getPathAndQuery(uri);
152 }
153 }
154 return uri == null? authorityString(request.authority()) : uri.toString();
155 }
156
157 void sendHeadersOnly() throws IOException {
158 collectHeaders();
159 chan.write(buffers, 0, 2);
160 }
161
162 void sendRequest() throws IOException {
163 collectHeaders();
164 if (contentLength == 0) {
165 chan.write(buffers, 0, 2);
166 } else if (contentLength > 0) {
167 writeFixedContent(true);
168 } else {
169 writeStreamedContent(true);
170 }
171 setFinished();
172 }
173
174 private boolean finished;
175
176 synchronized boolean finished() {
177 return finished;
178 }
179
180 synchronized void setFinished() {
181 finished = true;
182 }
183
184 private void collectHeaders() throws IOException {
185 if (Log.requests() && request != null) {
186 Log.logRequest(request.toString());
187 }
188 String uriString = requestURI();
189 StringBuilder sb = new StringBuilder(64);
190 sb.append(request.method())
191 .append(' ')
192 .append(uriString)
193 .append(" HTTP/1.1\r\n");
194 String cmd = sb.toString();
195
196 buffers[0] = ByteBuffer.wrap(cmd.getBytes(StandardCharsets.US_ASCII));
197 URI uri = request.uri();
198 if (uri != null) {
199 systemHeaders.setHeader("Host", uri.getHost());
200 }
201 if (request == null) {
202 // this is not a user request. No content
203 contentLength = 0;
204 } else {
205 contentLength = requestProc.onRequestStart(request, flowController);
206 }
207
208 if (contentLength == 0) {
209 systemHeaders.setHeader("Content-Length", "0");
210 collectHeaders0();
211 } else if (contentLength > 0) {
212 /* [0] request line [1] headers [2] body */
213 systemHeaders.setHeader("Content-Length",
214 Integer.toString((int) contentLength));
215 streaming = false;
216 collectHeaders0();
217 buffers[2] = chan.getBuffer();
218 } else {
219 /* Chunked:
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 */
24 package java.net.http;
25
26 import java.io.IOException;
27 import java.net.URI;
28 import java.nio.ByteBuffer;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.net.InetSocketAddress;
33 import java.net.http.HttpConnection.Mode;
34 import java.nio.charset.StandardCharsets;
35 import java.util.function.LongConsumer;
36 import static java.nio.charset.StandardCharsets.US_ASCII;
37
38 /**
39 * A HTTP/1.1 request.
40 *
41 * send() -> Writes the request + body to the given channel, in one blocking
42 * operation.
43 */
44 class Http1Request {
45
46 final HttpRequestImpl request;
47 final HttpConnection chan;
48 // Multiple buffers are used to hold different parts of request
49 // See line 206 and below for description
50 final ByteBuffer[] buffers;
51 final HttpRequest.BodyProcessor requestProc;
52 final HttpHeaders userHeaders;
53 final HttpHeadersImpl systemHeaders;
54 final LongConsumer flowController;
55 boolean streaming;
56 long contentLength;
57
58 Http1Request(HttpRequestImpl request, HttpConnection connection)
59 throws IOException
60 {
61 this.request = request;
62 this.chan = connection;
63 buffers = new ByteBuffer[5]; // TODO: check
64 this.requestProc = request.requestProcessor();
65 this.userHeaders = request.getUserHeaders();
66 this.systemHeaders = request.getSystemHeaders();
67 this.flowController = this::dummy;
68 }
69
70 private void logHeaders() throws IOException {
71 StringBuilder sb = new StringBuilder(256);
72 sb.append("REQUEST HEADERS:\r\n");
73 collectHeaders1(sb, request, systemHeaders);
76 }
77
78 private void dummy(long x) {
79 // not used in this class
80 }
81
82 private void collectHeaders0() throws IOException {
83 if (Log.headers()) {
84 logHeaders();
85 }
86 StringBuilder sb = new StringBuilder(256);
87 collectHeaders1(sb, request, systemHeaders);
88 collectHeaders1(sb, request, userHeaders);
89 sb.append("\r\n");
90 String headers = sb.toString();
91 buffers[1] = ByteBuffer.wrap(headers.getBytes(StandardCharsets.US_ASCII));
92 }
93
94 private void collectHeaders1(StringBuilder sb,
95 HttpRequestImpl request,
96 HttpHeaders headers)
97 throws IOException
98 {
99 Map<String,List<String>> h = headers.map();
100 Set<Map.Entry<String,List<String>>> entries = h.entrySet();
101
102 for (Map.Entry<String,List<String>> entry : entries) {
103 String key = entry.getKey();
104 sb.append(key).append(": ");
105 List<String> values = entry.getValue();
106 int num = values.size();
107 for (String value : values) {
108 sb.append(value);
109 if (--num > 0) {
110 sb.append(',');
111 }
112 }
113 sb.append("\r\n");
114 }
115 }
116
117 private String getPathAndQuery(URI uri) {
118 String path = uri.getPath();
119 String query = uri.getQuery();
120 if (path == null || path.equals("")) {
121 path = "/";
122 }
123 if (query == null) {
124 query = "";
125 }
126 if (query.equals("")) {
127 return path;
128 } else {
129 return path + "?" + query;
130 }
131 }
132
133 private String authorityString(InetSocketAddress addr) {
134 return addr.getHostString() + ":" + addr.getPort();
135 }
136
137 private String hostString() {
138 URI uri = request.uri();
139 int port = uri.getPort();
140 String host = uri.getHost();
141
142 boolean defaultPort;
143 if (port == -1)
144 defaultPort = true;
145 else if (request.secure())
146 defaultPort = port == 443;
147 else
148 defaultPort = port == 80;
149
150 if (defaultPort)
151 return host;
152 else
153 return host + ":" + Integer.toString(port);
154 }
155
156 private String requestURI() {
157 URI uri = request.uri();
158 String method = request.method();
159
160 if ((request.proxy() == null && !method.equals("CONNECT"))
161 || request.isWebSocket()) {
162 return getPathAndQuery(uri);
163 }
164 if (request.secure()) {
165 if (request.method().equals("CONNECT")) {
166 // use authority for connect itself
167 return authorityString(request.authority());
168 } else {
169 // requests over tunnel do not require full URL
170 return getPathAndQuery(uri);
171 }
172 }
173 return uri == null? authorityString(request.authority()) : uri.toString();
174 }
175
176 void sendHeadersOnly() throws IOException {
177 collectHeaders();
178 chan.write(buffers, 0, 2);
179 }
180
181 void sendRequest() throws IOException {
182 collectHeaders();
183 chan.configureMode(Mode.BLOCKING);
184 if (contentLength == 0) {
185 chan.write(buffers, 0, 2);
186 } else if (contentLength > 0) {
187 writeFixedContent(true);
188 } else {
189 writeStreamedContent(true);
190 }
191 setFinished();
192 }
193
194 private boolean finished;
195
196 synchronized boolean finished() {
197 return finished;
198 }
199
200 synchronized void setFinished() {
201 finished = true;
202 }
203
204 private void collectHeaders() throws IOException {
205 if (Log.requests() && request != null) {
206 Log.logRequest(request.toString());
207 }
208 String uriString = requestURI();
209 StringBuilder sb = new StringBuilder(64);
210 sb.append(request.method())
211 .append(' ')
212 .append(uriString)
213 .append(" HTTP/1.1\r\n");
214 String cmd = sb.toString();
215
216 buffers[0] = ByteBuffer.wrap(cmd.getBytes(StandardCharsets.US_ASCII));
217 URI uri = request.uri();
218 if (uri != null) {
219 systemHeaders.setHeader("Host", hostString());
220 }
221 if (request == null) {
222 // this is not a user request. No content
223 contentLength = 0;
224 } else {
225 contentLength = requestProc.onRequestStart(request, flowController);
226 }
227
228 if (contentLength == 0) {
229 systemHeaders.setHeader("Content-Length", "0");
230 collectHeaders0();
231 } else if (contentLength > 0) {
232 /* [0] request line [1] headers [2] body */
233 systemHeaders.setHeader("Content-Length",
234 Integer.toString((int) contentLength));
235 streaming = false;
236 collectHeaders0();
237 buffers[2] = chan.getBuffer();
238 } else {
239 /* Chunked:
|