mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-08-30 00:57:55 +00:00
Add User-Agent to Websockets
I think, haven't actually tested this :D:. Closes #730.
This commit is contained in:
@@ -3,185 +3,206 @@
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
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.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.apis.http.CheckUrl;
|
||||
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.*;
|
||||
import dan200.computercraft.core.apis.http.request.HttpRequest;
|
||||
import dan200.computercraft.core.apis.http.websocket.Websocket;
|
||||
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.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.
|
||||
*
|
||||
* @cc.module http
|
||||
* @hidden
|
||||
*/
|
||||
public class HTTPAPI implements ILuaAPI {
|
||||
private final IAPIEnvironment m_apiEnvironment;
|
||||
public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
private final IAPIEnvironment apiEnvironment;
|
||||
|
||||
private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>();
|
||||
private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>(() -> ComputerCraft.httpMaxRequests);
|
||||
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>(() -> ComputerCraft.httpMaxWebsockets);
|
||||
private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>( () -> ComputerCraft.httpMaxRequests );
|
||||
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>( () -> ComputerCraft.httpMaxWebsockets );
|
||||
|
||||
public HTTPAPI(IAPIEnvironment environment) {
|
||||
this.m_apiEnvironment = environment;
|
||||
public HTTPAPI( IAPIEnvironment environment )
|
||||
{
|
||||
apiEnvironment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getNames() {
|
||||
return new String[] {"http"};
|
||||
public String[] getNames()
|
||||
{
|
||||
return new String[] { "http" };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startup() {
|
||||
this.checkUrls.startup();
|
||||
this.requests.startup();
|
||||
this.websockets.startup();
|
||||
public void startup()
|
||||
{
|
||||
checkUrls.startup();
|
||||
requests.startup();
|
||||
websockets.startup();
|
||||
}
|
||||
|
||||
@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
|
||||
// resources as often as possible to reduce blocking.
|
||||
Resource.cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
this.checkUrls.shutdown();
|
||||
this.requests.shutdown();
|
||||
this.websockets.shutdown();
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] request(IArguments args) throws LuaException {
|
||||
public final Object[] request( IArguments args ) throws LuaException
|
||||
{
|
||||
String address, postString, requestMethod;
|
||||
Map<?, ?> headerTable;
|
||||
boolean binary, redirect;
|
||||
|
||||
if (args.get(0) instanceof Map) {
|
||||
Map<?, ?> options = args.getTable(0);
|
||||
address = getStringField(options, "url");
|
||||
postString = optStringField(options, "body", null);
|
||||
headerTable = optTableField(options, "headers", Collections.emptyMap());
|
||||
binary = optBooleanField(options, "binary", false);
|
||||
requestMethod = optStringField(options, "method", null);
|
||||
redirect = optBooleanField(options, "redirect", true);
|
||||
if( args.get( 0 ) instanceof Map )
|
||||
{
|
||||
Map<?, ?> options = args.getTable( 0 );
|
||||
address = getStringField( options, "url" );
|
||||
postString = optStringField( options, "body", null );
|
||||
headerTable = optTableField( options, "headers", Collections.emptyMap() );
|
||||
binary = optBooleanField( options, "binary", false );
|
||||
requestMethod = optStringField( options, "method", null );
|
||||
redirect = optBooleanField( options, "redirect", true );
|
||||
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get URL and post information
|
||||
address = args.getString(0);
|
||||
postString = args.optString(1, null);
|
||||
headerTable = args.optTable(2, Collections.emptyMap());
|
||||
binary = args.optBoolean(3, false);
|
||||
address = args.getString( 0 );
|
||||
postString = args.optString( 1, null );
|
||||
headerTable = args.optTable( 2, Collections.emptyMap() );
|
||||
binary = args.optBoolean( 3, false );
|
||||
requestMethod = null;
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
HttpHeaders headers = getHeaders(headerTable);
|
||||
HttpHeaders headers = getHeaders( headerTable );
|
||||
|
||||
HttpMethod httpMethod;
|
||||
if (requestMethod == null) {
|
||||
if( requestMethod == null )
|
||||
{
|
||||
httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
|
||||
} else {
|
||||
httpMethod = HttpMethod.valueOf(requestMethod.toUpperCase(Locale.ROOT));
|
||||
if (httpMethod == null || requestMethod.equalsIgnoreCase("CONNECT")) {
|
||||
throw new LuaException("Unsupported HTTP method");
|
||||
}
|
||||
else
|
||||
{
|
||||
httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
|
||||
if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
|
||||
{
|
||||
throw new LuaException( "Unsupported HTTP method" );
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
URI uri = HttpRequest.checkUri(address);
|
||||
HttpRequest request = new HttpRequest(this.requests, this.m_apiEnvironment, address, postString, headers, binary, redirect);
|
||||
try
|
||||
{
|
||||
URI uri = HttpRequest.checkUri( address );
|
||||
HttpRequest request = new HttpRequest( requests, apiEnvironment, address, postString, headers, binary, redirect );
|
||||
|
||||
// Make the request
|
||||
request.queue(r -> r.request(uri, httpMethod));
|
||||
request.queue( r -> r.request( uri, httpMethod ) );
|
||||
|
||||
return new Object[] {true};
|
||||
} catch (HTTPRequestException e) {
|
||||
return new Object[] {
|
||||
false,
|
||||
e.getMessage()
|
||||
};
|
||||
return new Object[] { true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
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
|
||||
private static HttpHeaders getHeaders(@Nonnull Map<?, ?> headerTable) throws LuaException {
|
||||
private HttpHeaders getHeaders( @Nonnull Map<?, ?> headerTable ) throws LuaException
|
||||
{
|
||||
HttpHeaders headers = new DefaultHttpHeaders();
|
||||
for (Map.Entry<?, ?> entry : headerTable.entrySet()) {
|
||||
for( Map.Entry<?, ?> entry : headerTable.entrySet() )
|
||||
{
|
||||
Object value = entry.getValue();
|
||||
if (entry.getKey() instanceof String && value instanceof String) {
|
||||
try {
|
||||
headers.add((String) entry.getKey(), value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new LuaException(e.getMessage());
|
||||
if( entry.getKey() instanceof String && value instanceof String )
|
||||
{
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@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()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,19 +3,8 @@
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
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.core.apis.handles.ArrayByteChannel;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
@@ -29,22 +18,20 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
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;
|
||||
import io.netty.handler.codec.http.*;
|
||||
|
||||
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}.
|
||||
*/
|
||||
@@ -53,16 +40,19 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
|
||||
private static final byte[] EMPTY_BYTES = new byte[0];
|
||||
|
||||
private final HttpRequest request;
|
||||
private boolean closed = false;
|
||||
|
||||
private final URI uri;
|
||||
private final HttpMethod method;
|
||||
private final Options options;
|
||||
private final HttpHeaders responseHeaders = new DefaultHttpHeaders();
|
||||
private boolean closed = false;
|
||||
|
||||
private Charset responseCharset;
|
||||
private final HttpHeaders responseHeaders = new DefaultHttpHeaders();
|
||||
private HttpResponseStatus responseStatus;
|
||||
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.uri = uri;
|
||||
@@ -71,203 +61,199 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (this.request.checkClosed()) {
|
||||
return;
|
||||
}
|
||||
public void channelActive( ChannelHandlerContext ctx ) throws Exception
|
||||
{
|
||||
if( request.checkClosed() ) return;
|
||||
|
||||
ByteBuf body = this.request.body();
|
||||
body.resetReaderIndex()
|
||||
.retain();
|
||||
ByteBuf body = request.body();
|
||||
body.resetReaderIndex().retain();
|
||||
|
||||
String requestUri = this.uri.getRawPath();
|
||||
if (this.uri.getRawQuery() != null) {
|
||||
requestUri += "?" + this.uri.getRawQuery();
|
||||
}
|
||||
String requestUri = uri.getRawPath();
|
||||
if( uri.getRawQuery() != null ) requestUri += "?" + uri.getRawQuery();
|
||||
|
||||
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, requestUri, body);
|
||||
request.setMethod(this.method);
|
||||
request.headers()
|
||||
.set(this.request.headers());
|
||||
FullHttpRequest request = new DefaultFullHttpRequest( HttpVersion.HTTP_1_1, HttpMethod.GET, requestUri, body );
|
||||
request.setMethod( method );
|
||||
request.headers().set( this.request.headers() );
|
||||
|
||||
// We force some headers to be always applied
|
||||
if (!request.headers()
|
||||
.contains(HttpHeaderNames.ACCEPT_CHARSET)) {
|
||||
request.headers()
|
||||
.set(HttpHeaderNames.ACCEPT_CHARSET, "UTF-8");
|
||||
if( !request.headers().contains( HttpHeaderNames.ACCEPT_CHARSET ) )
|
||||
{
|
||||
request.headers().set( HttpHeaderNames.ACCEPT_CHARSET, "UTF-8" );
|
||||
}
|
||||
if (!request.headers()
|
||||
.contains(HttpHeaderNames.USER_AGENT)) {
|
||||
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);
|
||||
request.headers().set( HttpHeaderNames.HOST, uri.getPort() < 0 ? uri.getHost() : uri.getHost() + ":" + uri.getPort() );
|
||||
request.headers().set( HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE );
|
||||
|
||||
ctx.channel()
|
||||
.writeAndFlush(request);
|
||||
ctx.channel().writeAndFlush( request );
|
||||
|
||||
super.channelActive(ctx);
|
||||
super.channelActive( ctx );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (!this.closed) {
|
||||
this.request.failure("Could not connect");
|
||||
}
|
||||
super.channelInactive(ctx);
|
||||
public void channelInactive( ChannelHandlerContext ctx ) throws Exception
|
||||
{
|
||||
if( !closed ) request.failure( "Could not connect" );
|
||||
super.channelInactive( ctx );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
if (ComputerCraft.logPeripheralErrors) {
|
||||
ComputerCraft.log.error("Error handling HTTP response", cause);
|
||||
}
|
||||
this.request.failure(cause);
|
||||
}
|
||||
public void channelRead0( ChannelHandlerContext ctx, HttpObject message )
|
||||
{
|
||||
if( closed || request.checkClosed() ) return;
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, HttpObject message) {
|
||||
if (this.closed || this.request.checkClosed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message instanceof HttpResponse) {
|
||||
if( message instanceof HttpResponse )
|
||||
{
|
||||
HttpResponse response = (HttpResponse) message;
|
||||
|
||||
if (this.request.redirects.get() > 0) {
|
||||
URI redirect = this.getRedirect(response.status(), response.headers());
|
||||
if (redirect != null && !this.uri.equals(redirect) && this.request.redirects.getAndDecrement() > 0) {
|
||||
if( request.redirects.get() > 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.
|
||||
|
||||
// We mark ourselves as disposed first though, to avoid firing events when the channel
|
||||
// becomes inactive or disposed.
|
||||
this.closed = true;
|
||||
closed = true;
|
||||
ctx.close();
|
||||
|
||||
try {
|
||||
HttpRequest.checkUri(redirect);
|
||||
} catch (HTTPRequestException e) {
|
||||
try
|
||||
{
|
||||
HttpRequest.checkUri( redirect );
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
// If we cannot visit this uri, then fail.
|
||||
this.request.failure(e.getMessage());
|
||||
request.failure( e.getMessage() );
|
||||
return;
|
||||
}
|
||||
|
||||
this.request.request(redirect,
|
||||
response.status()
|
||||
.code() == 303 ? HttpMethod.GET : this.method);
|
||||
request.request( redirect, response.status().code() == 303 ? HttpMethod.GET : method );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.responseCharset = HttpUtil.getCharset(response, StandardCharsets.UTF_8);
|
||||
this.responseStatus = response.status();
|
||||
this.responseHeaders.add(response.headers());
|
||||
responseCharset = HttpUtil.getCharset( response, StandardCharsets.UTF_8 );
|
||||
responseStatus = response.status();
|
||||
responseHeaders.add( response.headers() );
|
||||
}
|
||||
|
||||
if (message instanceof HttpContent) {
|
||||
if( message instanceof HttpContent )
|
||||
{
|
||||
HttpContent content = (HttpContent) message;
|
||||
|
||||
if (this.responseBody == null) {
|
||||
this.responseBody = ctx.alloc()
|
||||
.compositeBuffer(DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS);
|
||||
if( responseBody == null )
|
||||
{
|
||||
responseBody = ctx.alloc().compositeBuffer( DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS );
|
||||
}
|
||||
|
||||
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 (this.options.maxDownload != 0 && this.responseBody.readableBytes() + partial.readableBytes() > this.options.maxDownload) {
|
||||
this.closed = true;
|
||||
if( options.maxDownload != 0 && responseBody.readableBytes() + partial.readableBytes() > options.maxDownload )
|
||||
{
|
||||
closed = true;
|
||||
ctx.close();
|
||||
|
||||
this.request.failure("Response is too large");
|
||||
request.failure( "Response is too large" );
|
||||
return;
|
||||
}
|
||||
|
||||
this.responseBody.addComponent(true, partial.retain());
|
||||
responseBody.addComponent( true, partial.retain() );
|
||||
}
|
||||
|
||||
if (message instanceof LastHttpContent) {
|
||||
if( message instanceof LastHttpContent )
|
||||
{
|
||||
LastHttpContent last = (LastHttpContent) message;
|
||||
this.responseHeaders.add(last.trailingHeaders());
|
||||
responseHeaders.add( last.trailingHeaders() );
|
||||
|
||||
// Set the content length, if not already given.
|
||||
if (this.responseHeaders.contains(HttpHeaderNames.CONTENT_LENGTH)) {
|
||||
this.responseHeaders.set(HttpHeaderNames.CONTENT_LENGTH, this.responseBody.readableBytes());
|
||||
if( responseHeaders.contains( HttpHeaderNames.CONTENT_LENGTH ) )
|
||||
{
|
||||
responseHeaders.set( HttpHeaderNames.CONTENT_LENGTH, responseBody.readableBytes() );
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param status The status of the HTTP response.
|
||||
* @param status The status 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.
|
||||
*/
|
||||
private URI getRedirect(HttpResponseStatus status, HttpHeaders headers) {
|
||||
private URI getRedirect( HttpResponseStatus status, HttpHeaders headers )
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
public void close() {
|
||||
this.closed = true;
|
||||
if (this.responseBody != null) {
|
||||
this.responseBody.release();
|
||||
this.responseBody = null;
|
||||
public void close()
|
||||
{
|
||||
closed = true;
|
||||
if( responseBody != null )
|
||||
{
|
||||
responseBody.release();
|
||||
responseBody = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user