1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-28 16:13:22 +00:00

Allow multiple HTTP request methods

This implements an argument format similar to LuaReqeust, as described
in dan200/ComputerCraft#515. The Lua argument checking code is a little
verbose and repetitive, but I'm not sure how to avoid that - we should
look into improving it in the future.

Closes #21
This commit is contained in:
SquidDev 2018-05-15 10:10:23 +01:00
parent e4164ee9a1
commit 5bf9f9e3c5
4 changed files with 335 additions and 46 deletions

View File

@ -6,6 +6,7 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import com.google.common.collect.ImmutableSet;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
@ -21,9 +22,14 @@
import java.util.concurrent.Future; import java.util.concurrent.Future;
import static dan200.computercraft.core.apis.ArgumentHelper.*; import static dan200.computercraft.core.apis.ArgumentHelper.*;
import static dan200.computercraft.core.apis.TableHelper.*;
public class HTTPAPI implements ILuaAPI public class HTTPAPI implements ILuaAPI
{ {
private static final Set<String> HTTP_METHODS = ImmutableSet.of(
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE"
);
private final IAPIEnvironment m_apiEnvironment; private final IAPIEnvironment m_apiEnvironment;
private final List<Future<?>> m_httpTasks; private final List<Future<?>> m_httpTasks;
private final Set<Closeable> m_closeables; private final Set<Closeable> m_closeables;
@ -38,7 +44,7 @@ public HTTPAPI( IAPIEnvironment environment )
@Override @Override
public String[] getNames() public String[] getNames()
{ {
return new String[] { return new String[]{
"http" "http"
}; };
} }
@ -59,7 +65,7 @@ public void update()
} }
@Override @Override
public void shutdown( ) public void shutdown()
{ {
synchronized( m_httpTasks ) synchronized( m_httpTasks )
{ {
@ -89,7 +95,7 @@ public void shutdown( )
@Override @Override
public String[] getMethodNames() public String[] getMethodNames()
{ {
return new String[] { return new String[]{
"request", "request",
"checkURL", "checkURL",
"websocket", "websocket",
@ -101,52 +107,68 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O
{ {
switch( method ) switch( method )
{ {
case 0: case 0: // request
{ {
// request String urlString, postString, requestMethod;
// Get URL Map<Object, Object> headerTable;
String urlString = getString( args, 0 ); boolean binary, redirect;
// Get POST if( args.length >= 1 && args[0] instanceof Map )
String postString = optString( args, 1, null );
// Get Headers
Map<String, String> headers = null;
Map<Object, Object> table = optTable( args, 2, null );
if( table != null )
{ {
headers = new HashMap<>( table.size() ); Map<?, ?> options = (Map) args[0];
for( Object key : table.keySet() ) urlString = getStringField( options, "url" );
postString = optStringField( options, "body", null );
headerTable = optTableField( options, "headers", null );
binary = optBooleanField( options, "binary", false );
requestMethod = optStringField( options, "method", null );
redirect = optBooleanField( options, "redirect", true );
}
else
{
// Get URL and post information
urlString = getString( args, 0 );
postString = optString( args, 1, null );
headerTable = optTable( args, 2, null );
binary = optBoolean( args, 3, false );
requestMethod = null;
redirect = true;
}
Map<String, String> headers = null;
if( headerTable != null )
{
headers = new HashMap<>( headerTable.size() );
for( Object key : headerTable.keySet() )
{ {
Object value = table.get( key ); Object value = headerTable.get( key );
if( key instanceof String && value instanceof String ) if( key instanceof String && value instanceof String )
{ {
headers.put( (String)key, (String)value ); headers.put( (String) key, (String) value );
} }
} }
} }
// Get binary
boolean binary = false; if( requestMethod != null && !HTTP_METHODS.contains( requestMethod ) )
if( args.length >= 4 )
{ {
binary = args[ 3 ] != null && !args[ 3 ].equals( Boolean.FALSE ); throw new LuaException( "Unsupported HTTP method" );
} }
// Make the request // Make the request
try try
{ {
URL url = HTTPRequest.checkURL( urlString ); URL url = HTTPRequest.checkURL( urlString );
HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary ); HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary, requestMethod, redirect );
synchronized( m_httpTasks ) synchronized( m_httpTasks )
{ {
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) ); m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) );
} }
return new Object[] { true }; return new Object[]{ true };
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
return new Object[] { false, e.getMessage() }; return new Object[]{ false, e.getMessage() };
} }
} }
case 1: case 1:
@ -164,11 +186,11 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O
{ {
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) ); m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) );
} }
return new Object[] { true }; return new Object[]{ true };
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
return new Object[] { false, e.getMessage() }; return new Object[]{ false, e.getMessage() };
} }
} }
case 2: // websocket case 2: // websocket
@ -201,11 +223,11 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O
{ {
m_httpTasks.add( connector ); m_httpTasks.add( connector );
} }
return new Object[] { true }; return new Object[]{ true };
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
return new Object[] { false, e.getMessage() }; return new Object[]{ false, e.getMessage() };
} }
} }
default: default:

View File

@ -0,0 +1,214 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Various helpers for tables
*/
public final class TableHelper
{
private TableHelper()
{
throw new IllegalStateException( "Cannot instantiate singleton " + getClass().getName() );
}
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nullable Object actual )
{
return badKey( key, expected, ArgumentHelper.getType( actual ) );
}
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nonnull String actual )
{
return new LuaException( "bad field '" + key + "' (" + expected + " expected, got " + actual + ")" );
}
public static double getNumberField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static int getIntField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Number )
{
return (int) ((Number) value).longValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static double getRealField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
return checkReal( key, getNumberField( table, key ) );
}
public static boolean getBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badKey( key, "boolean", value );
}
}
@Nonnull
public static String getStringField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof String )
{
return (String) value;
}
else
{
throw badKey( key, "string", value );
}
}
@SuppressWarnings( "unchecked" )
@Nonnull
public static Map<Object, Object> getTableField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badKey( key, "table", value );
}
}
public static double optNumberField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static int optIntField( @Nonnull Map<?, ?> table, @Nonnull String key, int def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return (int) ((Number) value).longValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static double optRealField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
{
return checkReal( key, optNumberField( table, key, def ) );
}
public static boolean optBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key, boolean def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badKey( key, "boolean", value );
}
}
public static String optStringField( @Nonnull Map<?, ?> table, @Nonnull String key, String def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof String )
{
return (String) value;
}
else
{
throw badKey( key, "string", value );
}
}
@SuppressWarnings( "unchecked" )
public static Map<Object, Object> optTableField( @Nonnull Map<?, ?> table, @Nonnull String key, Map<Object, Object> def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badKey( key, "table", value );
}
}
private static double checkReal( @Nonnull String key, double value ) throws LuaException
{
if( Double.isNaN( value ) )
{
throw badKey( key, "number", "nan" );
}
else if( value == Double.POSITIVE_INFINITY )
{
throw badKey( key, "number", "inf" );
}
else if( value == Double.NEGATIVE_INFINITY )
{
throw badKey( key, "number", "-inf" );
}
else
{
return value;
}
}
}

View File

@ -79,8 +79,10 @@ public static InetAddress checkHost( String host ) throws HTTPRequestException
private final String m_postText; private final String m_postText;
private final Map<String, String> m_headers; private final Map<String, String> m_headers;
private boolean m_binary; private boolean m_binary;
private final String m_method;
private final boolean m_followRedirects;
public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary, final String method, final boolean followRedirects ) throws HTTPRequestException
{ {
m_environment = environment; m_environment = environment;
m_urlString = urlString; m_urlString = urlString;
@ -88,6 +90,8 @@ public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, fina
m_binary = binary; m_binary = binary;
m_postText = postText; m_postText = postText;
m_headers = headers; m_headers = headers;
m_method = method;
m_followRedirects = followRedirects;
} }
@Override @Override
@ -120,6 +124,8 @@ public void run()
{ {
connection.setRequestMethod( "GET" ); connection.setRequestMethod( "GET" );
} }
if( m_method != null ) connection.setRequestMethod( m_method );
connection.setInstanceFollowRedirects( m_followRedirects );
// Set headers // Set headers
connection.setRequestProperty( "accept-charset", "UTF-8" ); connection.setRequestProperty( "accept-charset", "UTF-8" );

View File

@ -662,8 +662,36 @@ end
if http then if http then
local nativeHTTPRequest = http.request local nativeHTTPRequest = http.request
local function wrapRequest( _url, _post, _headers, _binary ) local methods = {
local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary ) GET = true, POST = true, HEAD = true,
OPTIONS = true, PUT = true, DELETE = true
}
local function checkKey( options, key, ty, opt )
local value = options[key]
local valueTy = type(value)
if (value ~= nil or not opt) and valueTy ~= ty then
error(("bad field '%s' (expected %s, got %s"):format(key, ty, valueTy), 4)
end
end
local function checkOptions( options, body )
checkKey( options, "url", "string")
if body == false
then checkKey( options, "body", "nil" )
else checkKey( options, "body", "string", not body ) end
checkKey( options, "headers", "table", true )
checkKey( options, "method", "string", true )
checkKey( options, "redirect", "boolean", true )
if options.method and not methods[options.method] then
error( "Unsupported HTTP method", 3 )
end
end
local function wrapRequest( _url, ... )
local ok, err = nativeHTTPRequest( ... )
if ok then if ok then
while true do while true do
local event, param1, param2, param3 = os.pullEvent() local event, param1, param2, param3 = os.pullEvent()
@ -678,6 +706,11 @@ if http then
end end
http.get = function( _url, _headers, _binary) http.get = function( _url, _headers, _binary)
if type( _url ) == "table" then
checkOptions( _url, false )
return wrapRequest( _url.url, _url )
end
if type( _url ) ~= "string" then if type( _url ) ~= "string" then
error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 ) error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
end end
@ -687,10 +720,15 @@ if http then
if _binary ~= nil and type( _binary ) ~= "boolean" then if _binary ~= nil and type( _binary ) ~= "boolean" then
error( "bad argument #3 (expected boolean, got " .. type( _binary ) .. ")", 2 ) error( "bad argument #3 (expected boolean, got " .. type( _binary ) .. ")", 2 )
end end
return wrapRequest( _url, nil, _headers, _binary) return wrapRequest( _url, _url, nil, _headers, _binary )
end end
http.post = function( _url, _post, _headers, _binary) http.post = function( _url, _post, _headers, _binary)
if type( _url ) == "table" then
checkOptions( _url, true )
return wrapRequest( _url.url, _url )
end
if type( _url ) ~= "string" then if type( _url ) ~= "string" then
error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 ) error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
end end
@ -703,25 +741,34 @@ if http then
if _binary ~= nil and type( _binary ) ~= "boolean" then if _binary ~= nil and type( _binary ) ~= "boolean" then
error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 ) error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 )
end end
return wrapRequest( _url, _post or "", _headers, _binary) return wrapRequest( _url, _url, _post, _headers, _binary )
end end
http.request = function( _url, _post, _headers, _binary ) http.request = function( _url, _post, _headers, _binary )
if type( _url ) ~= "string" then local url
error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 ) if type( _url ) == "table" then
end checkOptions( _url )
if _post ~= nil and type( _post ) ~= "string" then url = _url.url
error( "bad argument #2 (expected string, got " .. type( _post ) .. ")", 2 ) else
end if type( _url ) ~= "string" then
if _headers ~= nil and type( _headers ) ~= "table" then error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
error( "bad argument #3 (expected table, got " .. type( _headers ) .. ")", 2 ) end
end if _post ~= nil and type( _post ) ~= "string" then
if _binary ~= nil and type( _binary ) ~= "boolean" then error( "bad argument #2 (expected string, got " .. type( _post ) .. ")", 2 )
error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 ) end
if _headers ~= nil and type( _headers ) ~= "table" then
error( "bad argument #3 (expected table, got " .. type( _headers ) .. ")", 2 )
end
if _binary ~= nil and type( _binary ) ~= "boolean" then
error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 )
end
url = _url.url
end end
local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary ) local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary )
if not ok then if not ok then
os.queueEvent( "http_failure", _url, err ) os.queueEvent( "http_failure", url, err )
end end
return ok, err return ok, err
end end