1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-09-02 10:37:54 +00:00

Add User-Agent to Websockets

I think, haven't actually tested this :D:. Closes #730.
This commit is contained in:
Jonathan Coates
2021-03-12 09:14:52 +00:00
committed by Jummit
parent 5b31c2536a
commit 3860e2466c
2 changed files with 281 additions and 274 deletions

View File

@@ -3,185 +3,206 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com * Send enquiries to dratcliffe@gmail.com
*/ */
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import static dan200.computercraft.core.apis.TableHelper.getStringField;
import static dan200.computercraft.core.apis.TableHelper.optBooleanField;
import static dan200.computercraft.core.apis.TableHelper.optStringField;
import static dan200.computercraft.core.apis.TableHelper.optTableField;
import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.IArguments; import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.apis.http.CheckUrl; import dan200.computercraft.core.apis.http.*;
import dan200.computercraft.core.apis.http.HTTPRequestException;
import dan200.computercraft.core.apis.http.Resource;
import dan200.computercraft.core.apis.http.ResourceGroup;
import dan200.computercraft.core.apis.http.ResourceQueue;
import dan200.computercraft.core.apis.http.request.HttpRequest; import dan200.computercraft.core.apis.http.request.HttpRequest;
import dan200.computercraft.core.apis.http.websocket.Websocket; import dan200.computercraft.core.apis.http.websocket.Websocket;
import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpMethod;
import javax.annotation.Nonnull;
import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import static dan200.computercraft.core.apis.TableHelper.*;
/** /**
* The http library allows communicating with web servers, sending and receiving data from them. * The http library allows communicating with web servers, sending and receiving data from them.
* *
* @cc.module http * @cc.module http
* @hidden * @hidden
*/ */
public class HTTPAPI implements ILuaAPI { public class HTTPAPI implements ILuaAPI
private final IAPIEnvironment m_apiEnvironment; {
private final IAPIEnvironment apiEnvironment;
private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>(); private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>();
private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>(() -> ComputerCraft.httpMaxRequests); private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>( () -> ComputerCraft.httpMaxRequests );
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>(() -> ComputerCraft.httpMaxWebsockets); private final ResourceGroup<Websocket> websockets = new ResourceGroup<>( () -> ComputerCraft.httpMaxWebsockets );
public HTTPAPI(IAPIEnvironment environment) { public HTTPAPI( IAPIEnvironment environment )
this.m_apiEnvironment = environment; {
apiEnvironment = environment;
} }
@Override @Override
public String[] getNames() { public String[] getNames()
return new String[] {"http"}; {
return new String[] { "http" };
} }
@Override @Override
public void startup() { public void startup()
this.checkUrls.startup(); {
this.requests.startup(); checkUrls.startup();
this.websockets.startup(); requests.startup();
websockets.startup();
} }
@Override @Override
public void update() { public void shutdown()
{
checkUrls.shutdown();
requests.shutdown();
websockets.shutdown();
}
@Override
public void update()
{
// It's rather ugly to run this here, but we need to clean up // It's rather ugly to run this here, but we need to clean up
// resources as often as possible to reduce blocking. // resources as often as possible to reduce blocking.
Resource.cleanup(); Resource.cleanup();
} }
@Override
public void shutdown() {
this.checkUrls.shutdown();
this.requests.shutdown();
this.websockets.shutdown();
}
@LuaFunction @LuaFunction
public final Object[] request(IArguments args) throws LuaException { public final Object[] request( IArguments args ) throws LuaException
{
String address, postString, requestMethod; String address, postString, requestMethod;
Map<?, ?> headerTable; Map<?, ?> headerTable;
boolean binary, redirect; boolean binary, redirect;
if (args.get(0) instanceof Map) { if( args.get( 0 ) instanceof Map )
Map<?, ?> options = args.getTable(0); {
address = getStringField(options, "url"); Map<?, ?> options = args.getTable( 0 );
postString = optStringField(options, "body", null); address = getStringField( options, "url" );
headerTable = optTableField(options, "headers", Collections.emptyMap()); postString = optStringField( options, "body", null );
binary = optBooleanField(options, "binary", false); headerTable = optTableField( options, "headers", Collections.emptyMap() );
requestMethod = optStringField(options, "method", null); binary = optBooleanField( options, "binary", false );
redirect = optBooleanField(options, "redirect", true); requestMethod = optStringField( options, "method", null );
redirect = optBooleanField( options, "redirect", true );
} else { }
else
{
// Get URL and post information // Get URL and post information
address = args.getString(0); address = args.getString( 0 );
postString = args.optString(1, null); postString = args.optString( 1, null );
headerTable = args.optTable(2, Collections.emptyMap()); headerTable = args.optTable( 2, Collections.emptyMap() );
binary = args.optBoolean(3, false); binary = args.optBoolean( 3, false );
requestMethod = null; requestMethod = null;
redirect = true; redirect = true;
} }
HttpHeaders headers = getHeaders(headerTable); HttpHeaders headers = getHeaders( headerTable );
HttpMethod httpMethod; HttpMethod httpMethod;
if (requestMethod == null) { if( requestMethod == null )
{
httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST; httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
} else { }
httpMethod = HttpMethod.valueOf(requestMethod.toUpperCase(Locale.ROOT)); else
if (httpMethod == null || requestMethod.equalsIgnoreCase("CONNECT")) { {
throw new LuaException("Unsupported HTTP method"); httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
{
throw new LuaException( "Unsupported HTTP method" );
} }
} }
try { try
URI uri = HttpRequest.checkUri(address); {
HttpRequest request = new HttpRequest(this.requests, this.m_apiEnvironment, address, postString, headers, binary, redirect); URI uri = HttpRequest.checkUri( address );
HttpRequest request = new HttpRequest( requests, apiEnvironment, address, postString, headers, binary, redirect );
// Make the request // Make the request
request.queue(r -> r.request(uri, httpMethod)); request.queue( r -> r.request( uri, httpMethod ) );
return new Object[] {true}; return new Object[] { true };
} catch (HTTPRequestException e) { }
return new Object[] { catch( HTTPRequestException e )
false, {
e.getMessage() return new Object[] { false, e.getMessage() };
}; }
}
@LuaFunction
public final Object[] checkURL( String address )
{
try
{
URI uri = HttpRequest.checkUri( address );
new CheckUrl( checkUrls, apiEnvironment, address, uri ).queue( CheckUrl::run );
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
@LuaFunction
public final Object[] websocket( String address, Optional<Map<?, ?>> headerTbl ) throws LuaException
{
if( !ComputerCraft.http_websocket_enable )
{
throw new LuaException( "Websocket connections are disabled" );
}
HttpHeaders headers = getHeaders( headerTbl.orElse( Collections.emptyMap() ) );
try
{
URI uri = Websocket.checkUri( address );
if( !new Websocket( websockets, apiEnvironment, uri, address, headers ).queue( Websocket::connect ) )
{
throw new LuaException( "Too many websockets already open" );
}
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
} }
} }
@Nonnull @Nonnull
private static HttpHeaders getHeaders(@Nonnull Map<?, ?> headerTable) throws LuaException { private HttpHeaders getHeaders( @Nonnull Map<?, ?> headerTable ) throws LuaException
{
HttpHeaders headers = new DefaultHttpHeaders(); HttpHeaders headers = new DefaultHttpHeaders();
for (Map.Entry<?, ?> entry : headerTable.entrySet()) { for( Map.Entry<?, ?> entry : headerTable.entrySet() )
{
Object value = entry.getValue(); Object value = entry.getValue();
if (entry.getKey() instanceof String && value instanceof String) { if( entry.getKey() instanceof String && value instanceof String )
try { {
headers.add((String) entry.getKey(), value); try
} catch (IllegalArgumentException e) { {
throw new LuaException(e.getMessage()); headers.add( (String) entry.getKey(), value );
}
catch( IllegalArgumentException e )
{
throw new LuaException( e.getMessage() );
} }
} }
} }
if( !headers.contains( HttpHeaderNames.USER_AGENT ) )
{
headers.set( HttpHeaderNames.USER_AGENT, apiEnvironment.getComputerEnvironment().getUserAgent() );
}
return headers; return headers;
} }
@LuaFunction
public final Object[] checkURL(String address) {
try {
URI uri = HttpRequest.checkUri(address);
new CheckUrl(this.checkUrls, this.m_apiEnvironment, address, uri).queue(CheckUrl::run);
return new Object[] {true};
} catch (HTTPRequestException e) {
return new Object[] {
false,
e.getMessage()
};
}
}
@LuaFunction
public final Object[] websocket(String address, Optional<Map<?, ?>> headerTbl) throws LuaException {
if (!ComputerCraft.http_websocket_enable) {
throw new LuaException("Websocket connections are disabled");
}
HttpHeaders headers = getHeaders(headerTbl.orElse(Collections.emptyMap()));
try {
URI uri = Websocket.checkUri(address);
if (!new Websocket(this.websockets, this.m_apiEnvironment, uri, address, headers).queue(Websocket::connect)) {
throw new LuaException("Too many websockets already open");
}
return new Object[] {true};
} catch (HTTPRequestException e) {
return new Object[] {
false,
e.getMessage()
};
}
}
} }

View File

@@ -3,19 +3,8 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com * Send enquiries to dratcliffe@gmail.com
*/ */
package dan200.computercraft.core.apis.http.request; package dan200.computercraft.core.apis.http.request;
import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize;
import java.io.Closeable;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.apis.handles.ArrayByteChannel; import dan200.computercraft.core.apis.handles.ArrayByteChannel;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle; import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
@@ -29,22 +18,20 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.CompositeByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpObject> implements Closeable { import java.io.Closeable;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize;
public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpObject> implements Closeable
{
/** /**
* Same as {@link io.netty.handler.codec.MessageAggregator}. * Same as {@link io.netty.handler.codec.MessageAggregator}.
*/ */
@@ -53,16 +40,19 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
private static final byte[] EMPTY_BYTES = new byte[0]; private static final byte[] EMPTY_BYTES = new byte[0];
private final HttpRequest request; private final HttpRequest request;
private boolean closed = false;
private final URI uri; private final URI uri;
private final HttpMethod method; private final HttpMethod method;
private final Options options; private final Options options;
private final HttpHeaders responseHeaders = new DefaultHttpHeaders();
private boolean closed = false;
private Charset responseCharset; private Charset responseCharset;
private final HttpHeaders responseHeaders = new DefaultHttpHeaders();
private HttpResponseStatus responseStatus; private HttpResponseStatus responseStatus;
private CompositeByteBuf responseBody; private CompositeByteBuf responseBody;
HttpRequestHandler(HttpRequest request, URI uri, HttpMethod method, Options options) { HttpRequestHandler( HttpRequest request, URI uri, HttpMethod method, Options options )
{
this.request = request; this.request = request;
this.uri = uri; this.uri = uri;
@@ -71,203 +61,199 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
} }
@Override @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelActive( ChannelHandlerContext ctx ) throws Exception
if (this.request.checkClosed()) { {
return; if( request.checkClosed() ) return;
}
ByteBuf body = this.request.body(); ByteBuf body = request.body();
body.resetReaderIndex() body.resetReaderIndex().retain();
.retain();
String requestUri = this.uri.getRawPath(); String requestUri = uri.getRawPath();
if (this.uri.getRawQuery() != null) { if( uri.getRawQuery() != null ) requestUri += "?" + uri.getRawQuery();
requestUri += "?" + this.uri.getRawQuery();
}
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, requestUri, body); FullHttpRequest request = new DefaultFullHttpRequest( HttpVersion.HTTP_1_1, HttpMethod.GET, requestUri, body );
request.setMethod(this.method); request.setMethod( method );
request.headers() request.headers().set( this.request.headers() );
.set(this.request.headers());
// We force some headers to be always applied // We force some headers to be always applied
if (!request.headers() if( !request.headers().contains( HttpHeaderNames.ACCEPT_CHARSET ) )
.contains(HttpHeaderNames.ACCEPT_CHARSET)) { {
request.headers() request.headers().set( HttpHeaderNames.ACCEPT_CHARSET, "UTF-8" );
.set(HttpHeaderNames.ACCEPT_CHARSET, "UTF-8");
} }
if (!request.headers() request.headers().set( HttpHeaderNames.HOST, uri.getPort() < 0 ? uri.getHost() : uri.getHost() + ":" + uri.getPort() );
.contains(HttpHeaderNames.USER_AGENT)) { request.headers().set( HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE );
request.headers()
.set(HttpHeaderNames.USER_AGENT,
this.request.environment()
.getComputerEnvironment()
.getUserAgent());
}
request.headers()
.set(HttpHeaderNames.HOST, this.uri.getPort() < 0 ? this.uri.getHost() : this.uri.getHost() + ":" + this.uri.getPort());
request.headers()
.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
ctx.channel() ctx.channel().writeAndFlush( request );
.writeAndFlush(request);
super.channelActive(ctx); super.channelActive( ctx );
} }
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive( ChannelHandlerContext ctx ) throws Exception
if (!this.closed) { {
this.request.failure("Could not connect"); if( !closed ) request.failure( "Could not connect" );
} super.channelInactive( ctx );
super.channelInactive(ctx);
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { public void channelRead0( ChannelHandlerContext ctx, HttpObject message )
if (ComputerCraft.logPeripheralErrors) { {
ComputerCraft.log.error("Error handling HTTP response", cause); if( closed || request.checkClosed() ) return;
}
this.request.failure(cause);
}
@Override if( message instanceof HttpResponse )
public void channelRead0(ChannelHandlerContext ctx, HttpObject message) { {
if (this.closed || this.request.checkClosed()) {
return;
}
if (message instanceof HttpResponse) {
HttpResponse response = (HttpResponse) message; HttpResponse response = (HttpResponse) message;
if (this.request.redirects.get() > 0) { if( request.redirects.get() > 0 )
URI redirect = this.getRedirect(response.status(), response.headers()); {
if (redirect != null && !this.uri.equals(redirect) && this.request.redirects.getAndDecrement() > 0) { URI redirect = getRedirect( response.status(), response.headers() );
if( redirect != null && !uri.equals( redirect ) && request.redirects.getAndDecrement() > 0 )
{
// If we have a redirect, and don't end up at the same place, then follow it. // If we have a redirect, and don't end up at the same place, then follow it.
// We mark ourselves as disposed first though, to avoid firing events when the channel // We mark ourselves as disposed first though, to avoid firing events when the channel
// becomes inactive or disposed. // becomes inactive or disposed.
this.closed = true; closed = true;
ctx.close(); ctx.close();
try { try
HttpRequest.checkUri(redirect); {
} catch (HTTPRequestException e) { HttpRequest.checkUri( redirect );
}
catch( HTTPRequestException e )
{
// If we cannot visit this uri, then fail. // If we cannot visit this uri, then fail.
this.request.failure(e.getMessage()); request.failure( e.getMessage() );
return; return;
} }
this.request.request(redirect, request.request( redirect, response.status().code() == 303 ? HttpMethod.GET : method );
response.status()
.code() == 303 ? HttpMethod.GET : this.method);
return; return;
} }
} }
this.responseCharset = HttpUtil.getCharset(response, StandardCharsets.UTF_8); responseCharset = HttpUtil.getCharset( response, StandardCharsets.UTF_8 );
this.responseStatus = response.status(); responseStatus = response.status();
this.responseHeaders.add(response.headers()); responseHeaders.add( response.headers() );
} }
if (message instanceof HttpContent) { if( message instanceof HttpContent )
{
HttpContent content = (HttpContent) message; HttpContent content = (HttpContent) message;
if (this.responseBody == null) { if( responseBody == null )
this.responseBody = ctx.alloc() {
.compositeBuffer(DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS); responseBody = ctx.alloc().compositeBuffer( DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS );
} }
ByteBuf partial = content.content(); ByteBuf partial = content.content();
if (partial.isReadable()) { if( partial.isReadable() )
{
// If we've read more than we're allowed to handle, abort as soon as possible. // If we've read more than we're allowed to handle, abort as soon as possible.
if (this.options.maxDownload != 0 && this.responseBody.readableBytes() + partial.readableBytes() > this.options.maxDownload) { if( options.maxDownload != 0 && responseBody.readableBytes() + partial.readableBytes() > options.maxDownload )
this.closed = true; {
closed = true;
ctx.close(); ctx.close();
this.request.failure("Response is too large"); request.failure( "Response is too large" );
return; return;
} }
this.responseBody.addComponent(true, partial.retain()); responseBody.addComponent( true, partial.retain() );
} }
if (message instanceof LastHttpContent) { if( message instanceof LastHttpContent )
{
LastHttpContent last = (LastHttpContent) message; LastHttpContent last = (LastHttpContent) message;
this.responseHeaders.add(last.trailingHeaders()); responseHeaders.add( last.trailingHeaders() );
// Set the content length, if not already given. // Set the content length, if not already given.
if (this.responseHeaders.contains(HttpHeaderNames.CONTENT_LENGTH)) { if( responseHeaders.contains( HttpHeaderNames.CONTENT_LENGTH ) )
this.responseHeaders.set(HttpHeaderNames.CONTENT_LENGTH, this.responseBody.readableBytes()); {
responseHeaders.set( HttpHeaderNames.CONTENT_LENGTH, responseBody.readableBytes() );
} }
ctx.close(); ctx.close();
this.sendResponse(); sendResponse();
} }
} }
} }
@Override
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
{
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause );
request.failure( cause );
}
private void sendResponse()
{
// Read the ByteBuf into a channel.
CompositeByteBuf body = responseBody;
byte[] bytes = body == null ? EMPTY_BYTES : NetworkUtils.toBytes( body );
// Decode the headers
HttpResponseStatus status = responseStatus;
Map<String, String> headers = new HashMap<>();
for( Map.Entry<String, String> header : responseHeaders )
{
String existing = headers.get( header.getKey() );
headers.put( header.getKey(), existing == null ? header.getValue() : existing + "," + header.getValue() );
}
// Fire off a stats event
request.environment().addTrackingChange( TrackingField.HTTP_DOWNLOAD, getHeaderSize( responseHeaders ) + bytes.length );
// Prepare to queue an event
ArrayByteChannel contents = new ArrayByteChannel( bytes );
HandleGeneric reader = request.isBinary()
? BinaryReadableHandle.of( contents )
: new EncodedReadableHandle( EncodedReadableHandle.open( contents, responseCharset ) );
HttpResponseHandle stream = new HttpResponseHandle( reader, status.code(), status.reasonPhrase(), headers );
if( status.code() >= 200 && status.code() < 400 )
{
request.success( stream );
}
else
{
request.failure( status.reasonPhrase(), stream );
}
}
/** /**
* Determine the redirect from this response. * Determine the redirect from this response.
* *
* @param status The status of the HTTP response. * @param status The status of the HTTP response.
* @param headers The headers of the HTTP response. * @param headers The headers of the HTTP response.
* @return The URI to redirect to, or {@code null} if no redirect should occur. * @return The URI to redirect to, or {@code null} if no redirect should occur.
*/ */
private URI getRedirect(HttpResponseStatus status, HttpHeaders headers) { private URI getRedirect( HttpResponseStatus status, HttpHeaders headers )
{
int code = status.code(); int code = status.code();
if (code < 300 || code > 307 || code == 304 || code == 306) { if( code < 300 || code > 307 || code == 304 || code == 306 ) return null;
String location = headers.get( HttpHeaderNames.LOCATION );
if( location == null ) return null;
try
{
return uri.resolve( new URI( location ) );
}
catch( IllegalArgumentException | URISyntaxException e )
{
return null; return null;
} }
String location = headers.get(HttpHeaderNames.LOCATION);
if (location == null) {
return null;
}
try {
return this.uri.resolve(new URI( location ));
} catch( IllegalArgumentException | URISyntaxException e ) {
return null;
}
}
private void sendResponse() {
// Read the ByteBuf into a channel.
CompositeByteBuf body = this.responseBody;
byte[] bytes = body == null ? EMPTY_BYTES : NetworkUtils.toBytes(body);
// Decode the headers
HttpResponseStatus status = this.responseStatus;
Map<String, String> headers = new HashMap<>();
for (Map.Entry<String, String> header : this.responseHeaders) {
String existing = headers.get(header.getKey());
headers.put(header.getKey(), existing == null ? header.getValue() : existing + "," + header.getValue());
}
// Fire off a stats event
this.request.environment()
.addTrackingChange(TrackingField.HTTP_DOWNLOAD, getHeaderSize(this.responseHeaders) + bytes.length);
// Prepare to queue an event
ArrayByteChannel contents = new ArrayByteChannel(bytes);
HandleGeneric reader = this.request.isBinary() ? BinaryReadableHandle.of(contents) : new EncodedReadableHandle(EncodedReadableHandle.open(contents,
this.responseCharset));
HttpResponseHandle stream = new HttpResponseHandle(reader, status.code(), status.reasonPhrase(), headers);
if (status.code() >= 200 && status.code() < 400) {
this.request.success(stream);
} else {
this.request.failure(status.reasonPhrase(), stream);
}
} }
@Override @Override
public void close() { public void close()
this.closed = true; {
if (this.responseBody != null) { closed = true;
this.responseBody.release(); if( responseBody != null )
this.responseBody = null; {
responseBody.release();
responseBody = null;
} }
} }
} }