private boolean isNeedToRead() { return !(read || content.isEmpty()); } }
@Override protected void beforeComplete(Subscriber<? super HttpObject> subscriber) { if (responseDecoder == null) { return; } final HttpData lastData = responseDecoder.finish(); if (!lastData.isEmpty()) { subscriber.onNext(lastData); } }
/** * Returns {@code true} if the content of the response with the given {@link HttpStatus} is expected to * be always empty (1xx, 204, 205 and 304 responses.) * * @throws IllegalArgumentException if the specified {@code content} or {@code trailingHeaders} are * non-empty when the content is always empty */ public static boolean isContentAlwaysEmptyWithValidation( HttpStatus status, HttpData content, HttpHeaders trailingHeaders) { if (!isContentAlwaysEmpty(status)) { return false; } if (!content.isEmpty()) { throw new IllegalArgumentException( "A " + status + " response must have empty content: " + content.length() + " byte(s)"); } if (!trailingHeaders.isEmpty()) { throw new IllegalArgumentException( "A " + status + " response must not have trailing headers: " + trailingHeaders); } return true; }
@Override public int doRead(ByteChunk chunk, Request request) throws IOException { if (read || content.isEmpty()) { // Read only once. return -1; } read = true; final int readableBytes = content.length(); chunk.setBytes(content.array(), content.offset(), readableBytes); return readableBytes; } }
/** * Creates a new HTTP response of the specified objects and closes the stream. */ static HttpResponse of(HttpHeaders headers, HttpData content, HttpHeaders trailingHeaders) { requireNonNull(headers, "headers"); requireNonNull(content, "content"); requireNonNull(trailingHeaders, "trailingHeaders"); final HttpStatus status = headers.status(); // From the section 8.1.2.4 of RFC 7540: //// For HTTP/2 responses, a single :status pseudo-header field is defined that carries the HTTP status //// code field (see [RFC7231], Section 6). This pseudo-header field MUST be included in all responses; //// otherwise, the response is malformed (Section 8.1.2.6). if (status == null) { throw new IllegalStateException("not a response (missing :status)"); } final HttpHeaders newHeaders = setOrRemoveContentLength(headers, content, trailingHeaders); if (content.isEmpty() && trailingHeaders.isEmpty()) { ReferenceCountUtil.safeRelease(content); return new OneElementFixedHttpResponse(newHeaders); } if (!content.isEmpty()) { if (trailingHeaders.isEmpty()) { return new TwoElementFixedHttpResponse(newHeaders, content); } else { return new RegularFixedHttpResponse(newHeaders, content, trailingHeaders); } } return new TwoElementFixedHttpResponse(newHeaders, trailingHeaders); }
/** * Converts the {@link AggregatedHttpMessage} into a new complete {@link HttpResponse}. */ static HttpResponse of(AggregatedHttpMessage res) { requireNonNull(res, "res"); final List<HttpHeaders> informationals = res.informationals(); final HttpHeaders headers = res.headers(); final HttpData content = res.content(); final HttpHeaders trailingHeaders = res.trailingHeaders(); if (informationals.isEmpty()) { return of(headers, content, trailingHeaders); } final int numObjects = informationals.size() + 1 /* headers */ + (!content.isEmpty() ? 1 : 0) + (!trailingHeaders.isEmpty() ? 1 : 0); final HttpObject[] objs = new HttpObject[numObjects]; int writerIndex = 0; for (HttpHeaders informational : informationals) { objs[writerIndex++] = informational; } objs[writerIndex++] = headers; if (!content.isEmpty()) { objs[writerIndex++] = content; } if (!trailingHeaders.isEmpty()) { objs[writerIndex] = trailingHeaders; } return new RegularFixedHttpResponse(objs); }
/** * Creates a new HTTP message. * * @param informationals the informational class (1xx) HTTP headers * @param headers the HTTP headers * @param content the content of the HTTP message * @param trailingHeaders the trailing HTTP headers */ static AggregatedHttpMessage of(Iterable<HttpHeaders> informationals, HttpHeaders headers, HttpData content, HttpHeaders trailingHeaders) { requireNonNull(informationals, "informationals"); requireNonNull(headers, "headers"); requireNonNull(content, "content"); requireNonNull(trailingHeaders, "trailingHeaders"); // Set the 'content-length' header if possible. final HttpStatus status = headers.status(); final HttpHeaders newHeaders; if (status != null) { // Response newHeaders = setOrRemoveContentLength(headers, content, trailingHeaders); } else { // Request newHeaders = headers.toMutable(); if (content.isEmpty()) { newHeaders.remove(CONTENT_LENGTH); } else { newHeaders.setInt(CONTENT_LENGTH, content.length()); } } return new DefaultAggregatedHttpMessage(ImmutableList.copyOf(informationals), newHeaders, content, trailingHeaders); }
@Override public void onComplete() { final Iterator<HttpObject> it = received.build().iterator(); final HttpHeaders headers = (HttpHeaders) it.next(); assertThat(headers.status()).isEqualTo(HttpStatus.OK); assertThat(headers.contentType()).isEqualTo(MediaType.JSON_SEQ); // JSON Text Sequences: *(Record Separator[0x1E] JSON-text Line Feed[0x0A]) assertThat(((HttpData) it.next()).array()) .isEqualTo(new byte[] { 0x1E, '\"', 'a', '\"', 0x0A }); assertThat(((HttpData) it.next()).array()) .isEqualTo(new byte[] { 0x1E, '\"', 'b', '\"', 0x0A }); assertThat(((HttpData) it.next()).array()) .isEqualTo(new byte[] { 0x1E, '\"', 'c', '\"', 0x0A }); assertThat(((HttpData) it.next()).isEmpty()).isTrue(); assertThat(it.hasNext()).isFalse(); isFinished.set(true); } });
@Test public void testSync_OnewayHelloService_hello() throws Exception { final AtomicReference<String> actualName = new AtomicReference<>(); final OnewayHelloService.Client client = new OnewayHelloService.Client.Factory().getClient(inProto, outProto); client.send_hello(FOO); assertThat(out.length()).isGreaterThan(0); final THttpService service = THttpService.of( (OnewayHelloService.Iface) actualName::set, defaultSerializationFormat); invoke(service); assertThat(promise.get().isEmpty()).isTrue(); assertThat(actualName.get()).isEqualTo(FOO); }
@Test public void testAsync_OnewayHelloService_hello() throws Exception { final AtomicReference<String> actualName = new AtomicReference<>(); final OnewayHelloService.Client client = new OnewayHelloService.Client.Factory().getClient(inProto, outProto); client.send_hello(FOO); assertThat(out.length()).isGreaterThan(0); final THttpService service = THttpService.of((OnewayHelloService.AsyncIface) (name, resultHandler) -> { actualName.set(name); resultHandler.onComplete(null); }, defaultSerializationFormat); invoke(service); assertThat(promise.get().isEmpty()).isTrue(); assertThat(actualName.get()).isEqualTo(FOO); }
write(headers); if (!content.isEmpty()) { write(content);
@Override protected ChannelFuture doWriteData(int id, int streamId, HttpData data, boolean endStream) { if (isStreamPresentAndWritable(streamId)) { // Write to an existing stream. return encoder.writeData(ctx, streamId, toByteBuf(data), 0, endStream, ctx.newPromise()); } if (encoder.connection().local().mayHaveCreatedStream(streamId)) { // Can't write to an outdated (closed) stream. ReferenceCountUtil.safeRelease(data); return data.isEmpty() ? ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) : newFailedFuture(ClosedPublisherException.get()); } // Cannot start a new stream with a DATA frame. It must start with a HEADERS frame. ReferenceCountUtil.safeRelease(data); return newFailedFuture(new IllegalStateException( "cannot start a new stream " + streamId + " with a DATA frame")); }
write(headers); if (!content.isEmpty()) { write(content);
@Test public void maybe() { final HttpClient client = HttpClient.of(rule.uri("/maybe")); AggregatedHttpMessage msg; msg = client.get("/string").aggregate().join(); assertThat(msg.headers().contentType()).isEqualTo(MediaType.PLAIN_TEXT_UTF_8); assertThat(msg.content().toStringUtf8()).isEqualTo("a"); msg = client.get("/json").aggregate().join(); assertThat(msg.headers().contentType()).isEqualTo(MediaType.JSON_UTF_8); assertThatJson(msg.content().toStringUtf8()).isStringEqualTo("a"); msg = client.get("/empty").aggregate().join(); assertThat(msg.headers().status()).isEqualTo(HttpStatus.OK); assertThat(msg.content().isEmpty()).isTrue(); msg = client.get("/error").aggregate().join(); assertThat(msg.headers().status()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); }
private static void fillRequest( ServiceRequestContext ctx, AggregatedHttpMessage aReq, Request jReq) { jReq.setDispatcherType(DispatcherType.REQUEST); jReq.setAsyncSupported(false, "armeria"); jReq.setSecure(ctx.sessionProtocol().isTls()); jReq.setMetaData(toRequestMetadata(ctx, aReq)); final HttpData content = aReq.content(); if (!content.isEmpty()) { jReq.getHttpInput().addContent(new Content(ByteBuffer.wrap( content.array(), content.offset(), content.length()))); } jReq.getHttpInput().eof(); }
final boolean contentAndTrailingHeadersEmpty = content.isEmpty() && trailingHeadersEmpty;
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) || !content.isEmpty()) { final HttpHeaders mutable = headers.toMutable(); mutable.setInt(HttpHeaderNames.CONTENT_LENGTH, content.length());
private void failAndRespond(Throwable cause, AggregatedHttpMessage message, Http2Error error) { final HttpHeaders headers = message.headers(); final HttpData content = message.content(); logBuilder().responseHeaders(headers); logBuilder().increaseResponseLength(content.length()); final State oldState = setDone(); subscription.cancel(); final int id = req.id(); final int streamId = req.streamId(); final ChannelFuture future; if (wroteNothing(oldState)) { // Did not write anything yet; we can send an error response instead of resetting the stream. if (content.isEmpty()) { future = responseEncoder.writeHeaders(id, streamId, headers, true); } else { responseEncoder.writeHeaders(id, streamId, headers, false); future = responseEncoder.writeData(id, streamId, content, true); } } else { // Wrote something already; we have to reset/cancel the stream. future = responseEncoder.writeReset(id, streamId, error); } addCallbackAndFlush(cause, oldState, future); }
if (content.isEmpty()) { throw new TApplicationException(TApplicationException.MISSING_RESULT);