mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-30 21:23:00 +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:
		| @@ -6,6 +6,7 @@ | ||||
|  | ||||
| package dan200.computercraft.core.apis; | ||||
|  | ||||
| import com.google.common.collect.ImmutableSet; | ||||
| import dan200.computercraft.ComputerCraft; | ||||
| import dan200.computercraft.api.lua.ILuaAPI; | ||||
| import dan200.computercraft.api.lua.ILuaContext; | ||||
| @@ -21,9 +22,14 @@ import java.util.*; | ||||
| import java.util.concurrent.Future; | ||||
|  | ||||
| import static dan200.computercraft.core.apis.ArgumentHelper.*; | ||||
| import static dan200.computercraft.core.apis.TableHelper.*; | ||||
|  | ||||
| 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 List<Future<?>> m_httpTasks; | ||||
|     private final Set<Closeable> m_closeables; | ||||
| @@ -38,7 +44,7 @@ public class HTTPAPI implements ILuaAPI | ||||
|     @Override | ||||
|     public String[] getNames() | ||||
|     { | ||||
|         return new String[] { | ||||
|         return new String[]{ | ||||
|             "http" | ||||
|         }; | ||||
|     } | ||||
| @@ -59,7 +65,7 @@ public class HTTPAPI implements ILuaAPI | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void shutdown( ) | ||||
|     public void shutdown() | ||||
|     { | ||||
|         synchronized( m_httpTasks ) | ||||
|         { | ||||
| @@ -89,7 +95,7 @@ public class HTTPAPI implements ILuaAPI | ||||
|     @Override | ||||
|     public String[] getMethodNames() | ||||
|     { | ||||
|         return new String[] { | ||||
|         return new String[]{ | ||||
|             "request", | ||||
|             "checkURL", | ||||
|             "websocket", | ||||
| @@ -101,52 +107,68 @@ public class HTTPAPI implements ILuaAPI | ||||
|     { | ||||
|         switch( method ) | ||||
|         { | ||||
|             case 0: | ||||
|             case 0: // request | ||||
|             { | ||||
|                 // request | ||||
|                 // Get URL | ||||
|                 String urlString = getString( args, 0 ); | ||||
|                 String urlString, postString, requestMethod; | ||||
|                 Map<Object, Object> headerTable; | ||||
|                 boolean binary, redirect; | ||||
|  | ||||
|                 // Get POST | ||||
|                 String postString = optString( args, 1, null ); | ||||
|  | ||||
|                 // Get Headers | ||||
|                 Map<String, String> headers = null; | ||||
|                 Map<Object, Object> table = optTable( args, 2, null ); | ||||
|                 if( table != null ) | ||||
|                 if( args.length >= 1 && args[0] instanceof Map ) | ||||
|                 { | ||||
|                     headers = new HashMap<>( table.size() ); | ||||
|                     for( Object key : table.keySet() ) | ||||
|                     Map<?, ?> options = (Map) args[0]; | ||||
|                     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 ) | ||||
|                         { | ||||
|                             headers.put( (String)key, (String)value ); | ||||
|                             headers.put( (String) key, (String) value ); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // Get binary | ||||
|                 boolean binary = false; | ||||
|                 if( args.length >= 4 ) | ||||
|  | ||||
|                 if( requestMethod != null && !HTTP_METHODS.contains( requestMethod ) ) | ||||
|                 { | ||||
|                     binary = args[ 3 ] != null && !args[ 3 ].equals( Boolean.FALSE ); | ||||
|                     throw new LuaException( "Unsupported HTTP method" ); | ||||
|                 } | ||||
|  | ||||
|                 // Make the request | ||||
|                 try | ||||
|                 { | ||||
|                     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 ) | ||||
|                     { | ||||
|                         m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) ); | ||||
|                     } | ||||
|                     return new Object[] { true }; | ||||
|                     return new Object[]{ true }; | ||||
|                 } | ||||
|                 catch( HTTPRequestException e ) | ||||
|                 { | ||||
|                     return new Object[] { false, e.getMessage() }; | ||||
|                     return new Object[]{ false, e.getMessage() }; | ||||
|                 } | ||||
|             } | ||||
|             case 1: | ||||
| @@ -164,11 +186,11 @@ public class HTTPAPI implements ILuaAPI | ||||
|                     { | ||||
|                         m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) ); | ||||
|                     } | ||||
|                     return new Object[] { true }; | ||||
|                     return new Object[]{ true }; | ||||
|                 } | ||||
|                 catch( HTTPRequestException e ) | ||||
|                 { | ||||
|                     return new Object[] { false, e.getMessage() }; | ||||
|                     return new Object[]{ false, e.getMessage() }; | ||||
|                 } | ||||
|             } | ||||
|             case 2: // websocket | ||||
| @@ -201,11 +223,11 @@ public class HTTPAPI implements ILuaAPI | ||||
|                     { | ||||
|                         m_httpTasks.add( connector ); | ||||
|                     } | ||||
|                     return new Object[] { true }; | ||||
|                     return new Object[]{ true }; | ||||
|                 } | ||||
|                 catch( HTTPRequestException e ) | ||||
|                 { | ||||
|                     return new Object[] { false, e.getMessage() }; | ||||
|                     return new Object[]{ false, e.getMessage() }; | ||||
|                 } | ||||
|             } | ||||
|             default: | ||||
|   | ||||
							
								
								
									
										214
									
								
								src/main/java/dan200/computercraft/core/apis/TableHelper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								src/main/java/dan200/computercraft/core/apis/TableHelper.java
									
									
									
									
									
										Normal 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -79,8 +79,10 @@ public class HTTPRequest implements Runnable | ||||
|     private final String m_postText; | ||||
|     private final Map<String, String> m_headers; | ||||
|     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_urlString = urlString; | ||||
| @@ -88,6 +90,8 @@ public class HTTPRequest implements Runnable | ||||
|         m_binary = binary; | ||||
|         m_postText = postText; | ||||
|         m_headers = headers; | ||||
|         m_method = method; | ||||
|         m_followRedirects = followRedirects; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -120,6 +124,8 @@ public class HTTPRequest implements Runnable | ||||
|             { | ||||
|                 connection.setRequestMethod( "GET" ); | ||||
|             } | ||||
|             if( m_method != null ) connection.setRequestMethod( m_method ); | ||||
|             connection.setInstanceFollowRedirects( m_followRedirects ); | ||||
|  | ||||
|             // Set headers | ||||
|             connection.setRequestProperty( "accept-charset", "UTF-8" ); | ||||
|   | ||||
| @@ -662,8 +662,36 @@ end | ||||
| if http then | ||||
|     local nativeHTTPRequest = http.request | ||||
|  | ||||
|     local function wrapRequest( _url, _post, _headers, _binary ) | ||||
|         local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary ) | ||||
|     local methods = { | ||||
|         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 | ||||
|             while true do | ||||
|                 local event, param1, param2, param3 = os.pullEvent() | ||||
| @@ -678,6 +706,11 @@ if http then | ||||
|     end | ||||
|      | ||||
|     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 | ||||
|             error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )  | ||||
|         end | ||||
| @@ -687,10 +720,15 @@ if http then | ||||
|         if _binary ~= nil and type( _binary ) ~= "boolean" then | ||||
|             error( "bad argument #3 (expected boolean, got " .. type( _binary ) .. ")", 2 )  | ||||
|         end | ||||
|         return wrapRequest( _url, nil, _headers, _binary) | ||||
|         return wrapRequest( _url, _url, nil, _headers, _binary ) | ||||
|     end | ||||
|  | ||||
|     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 | ||||
|             error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )  | ||||
|         end | ||||
| @@ -703,25 +741,34 @@ if http then | ||||
|         if _binary ~= nil and type( _binary ) ~= "boolean" then | ||||
|             error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 )  | ||||
|         end | ||||
|         return wrapRequest( _url, _post or "", _headers, _binary) | ||||
|         return wrapRequest( _url, _url, _post, _headers, _binary ) | ||||
|     end | ||||
|  | ||||
|     http.request = function( _url, _post, _headers, _binary ) | ||||
|         if type( _url ) ~= "string" then | ||||
|             error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )  | ||||
|         end | ||||
|         if _post ~= nil and type( _post ) ~= "string" then | ||||
|             error( "bad argument #2 (expected string, got " .. type( _post ) .. ")", 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 )  | ||||
|         local url | ||||
|         if type( _url ) == "table" then | ||||
|             checkOptions( _url ) | ||||
|             url = _url.url | ||||
|         else | ||||
|             if type( _url ) ~= "string" then | ||||
|                 error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )  | ||||
|             end | ||||
|             if _post ~= nil and type( _post ) ~= "string" then | ||||
|                 error( "bad argument #2 (expected string, got " .. type( _post ) .. ")", 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 | ||||
|  | ||||
|         local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary ) | ||||
|         if not ok then | ||||
|             os.queueEvent( "http_failure", _url, err ) | ||||
|             os.queueEvent( "http_failure", url, err ) | ||||
|         end | ||||
|         return ok, err | ||||
|     end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 SquidDev
					SquidDev