Skip to content

Commit

Permalink
Merge pull request dan200#395 from SquidDev-CC/ComputerCraft/feature/…
Browse files Browse the repository at this point in the history
…websocket

Websocket support
  • Loading branch information
SquidDev committed Nov 15, 2017
2 parents 2155fce + 30f4e08 commit c9181a1
Show file tree
Hide file tree
Showing 9 changed files with 568 additions and 165 deletions.
6 changes: 6 additions & 0 deletions src/main/java/dan200/computercraft/ComputerCraft.java
Expand Up @@ -121,6 +121,7 @@ public class ComputerCraft
};

public static boolean http_enable = true;
public static boolean http_websocket_enable = true;
public static AddressPredicate http_whitelist = new AddressPredicate( DEFAULT_HTTP_WHITELIST );
public static AddressPredicate http_blacklist = new AddressPredicate( DEFAULT_HTTP_BLACKLIST );
public static boolean disable_lua51_features = false;
Expand Down Expand Up @@ -200,6 +201,7 @@ public static class Config {
public static Configuration config;

public static Property http_enable;
public static Property http_websocket_enable;
public static Property http_whitelist;
public static Property http_blacklist;
public static Property disable_lua51_features;
Expand Down Expand Up @@ -271,6 +273,9 @@ public void preInit( FMLPreInitializationEvent event )
Config.http_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_enable", http_enable );
Config.http_enable.setComment( "Enable the \"http\" API on Computers (see \"http_whitelist\" and \"http_blacklist\" for more fine grained control than this)" );

Config.http_websocket_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_websocket_enable", http_websocket_enable );
Config.http_websocket_enable.setComment( "Enable use of http websockets. This requires the \"http_enable\" option to also be true." );

{
ConfigCategory category = Config.config.getCategory( Configuration.CATEGORY_GENERAL );
Property currentProperty = category.get( "http_whitelist" );
Expand Down Expand Up @@ -362,6 +367,7 @@ public void preInit( FMLPreInitializationEvent event )
public static void syncConfig() {

http_enable = Config.http_enable.getBoolean();
http_websocket_enable = Config.http_websocket_enable.getBoolean();
http_whitelist = new AddressPredicate( Config.http_whitelist.getStringList() );
http_blacklist = new AddressPredicate( Config.http_blacklist.getStringList() );
disable_lua51_features = Config.disable_lua51_features.getBoolean();
Expand Down
113 changes: 93 additions & 20 deletions src/main/java/dan200/computercraft/core/apis/HTTPAPI.java
Expand Up @@ -6,29 +6,37 @@

package dan200.computercraft.core.apis;

import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.http.HTTPCheck;
import dan200.computercraft.core.apis.http.HTTPExecutor;
import dan200.computercraft.core.apis.http.HTTPRequest;
import dan200.computercraft.core.apis.http.HTTPTask;
import dan200.computercraft.core.apis.http.WebsocketConnector;

import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Future;

import static dan200.computercraft.core.apis.ArgumentHelper.*;

public class HTTPAPI implements ILuaAPI
{
private final IAPIEnvironment m_apiEnvironment;
private final List<HTTPTask> m_httpTasks;

private final List<Future<?>> m_httpTasks;
private final Set<Closeable> m_closeables;

public HTTPAPI( IAPIEnvironment environment )
{
m_apiEnvironment = environment;
m_httpTasks = new ArrayList<>();
m_closeables = new HashSet<>();
}

@Override
public String[] getNames()
{
Expand All @@ -48,15 +56,11 @@ public void advance( double _dt )
// Wait for all of our http requests
synchronized( m_httpTasks )
{
Iterator<HTTPTask> it = m_httpTasks.iterator();
Iterator<Future<?>> it = m_httpTasks.iterator();
while( it.hasNext() )
{
final HTTPTask h = it.next();
if( h.isFinished() )
{
h.whenFinished( m_apiEnvironment );
it.remove();
}
final Future<?> h = it.next();
if( h.isDone() ) it.remove();
}
}
}
Expand All @@ -66,21 +70,36 @@ public void shutdown( )
{
synchronized( m_httpTasks )
{
for( HTTPTask r : m_httpTasks )
for( Future<?> r : m_httpTasks )
{
r.cancel();
r.cancel( false );
}
m_httpTasks.clear();
}
synchronized( m_closeables )
{
for( Closeable x : m_closeables )
{
try
{
x.close();
}
catch( IOException ignored )
{
}
}
m_closeables.clear();
}
}

@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
return new String[] {
"request",
"checkURL"
"checkURL",
"websocket",
};
}

Expand Down Expand Up @@ -125,10 +144,10 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O
try
{
URL url = HTTPRequest.checkURL( urlString );
HTTPRequest request = new HTTPRequest( urlString, url, postString, headers, binary );
HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary );
synchronized( m_httpTasks )
{
m_httpTasks.add( HTTPTask.submit( request ) );
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) );
}
return new Object[] { true };
}
Expand All @@ -147,9 +166,47 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O
try
{
URL url = HTTPRequest.checkURL( urlString );
HTTPCheck check = new HTTPCheck( urlString, url );
synchronized( m_httpTasks ) {
m_httpTasks.add( HTTPTask.submit( check ) );
HTTPCheck check = new HTTPCheck( m_apiEnvironment, urlString, url );
synchronized( m_httpTasks )
{
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) );
}
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
case 2: // websocket
{
String address = getString( args, 0 );
Map<Object, Object> headerTbl = optTable( args, 1, Collections.emptyMap() );

HashMap<String, String> headers = new HashMap<String, String>( headerTbl.size() );
for( Object key : headerTbl.keySet() )
{
Object value = headerTbl.get( key );
if( key instanceof String && value instanceof String )
{
headers.put( (String) key, (String) value );
}
}

if( !ComputerCraft.http_websocket_enable )
{
throw new LuaException( "Websocket connections are disabled" );
}

try
{
URI uri = WebsocketConnector.checkURI( address );
int port = WebsocketConnector.getPort( uri );

Future<?> connector = WebsocketConnector.createConnector( m_apiEnvironment, this, uri, address, port, headers );
synchronized( m_httpTasks )
{
m_httpTasks.add( connector );
}
return new Object[] { true };
}
Expand All @@ -164,4 +221,20 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O
}
}
}

public void addCloseable( Closeable closeable )
{
synchronized( m_closeables )
{
m_closeables.add( closeable );
}
}

public void removeCloseable( Closeable closeable )
{
synchronized( m_closeables )
{
m_closeables.remove( closeable );
}
}
}
25 changes: 7 additions & 18 deletions src/main/java/dan200/computercraft/core/apis/http/HTTPCheck.java
Expand Up @@ -5,14 +5,15 @@

import java.net.URL;

public class HTTPCheck implements HTTPTask.IHTTPTask
public class HTTPCheck implements Runnable
{
private final IAPIEnvironment environment;
private final String urlString;
private final URL url;
private String error;

public HTTPCheck( String urlString, URL url )
public HTTPCheck( IAPIEnvironment environment, String urlString, URL url )
{
this.environment = environment;
this.urlString = urlString;
this.url = url;
}
Expand All @@ -22,24 +23,12 @@ public void run()
{
try
{
HTTPRequest.checkHost( url );
}
catch( HTTPRequestException e )
{
error = e.getMessage();
}
}

@Override
public void whenFinished( IAPIEnvironment environment )
{
if( error == null )
{
HTTPRequest.checkHost( url.getHost() );
environment.queueEvent( "http_check", new Object[] { urlString, true } );
}
else
catch( HTTPRequestException e )
{
environment.queueEvent( "http_check", new Object[] { urlString, false, error } );
environment.queueEvent( "http_check", new Object[] { urlString, false, e.getMessage() } );
}
}
}
@@ -0,0 +1,45 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/

package dan200.computercraft.core.apis.http;

import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* Just a shared object for executing simple HTTP related tasks.
*/
public final class HTTPExecutor
{
public static final ListeningExecutorService EXECUTOR = MoreExecutors.listeningDecorator( new ThreadPoolExecutor(
4, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setDaemon( true )
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
.setNameFormat( "ComputerCraft-HTTP-%d" )
.build()
) );

public static final EventLoopGroup LOOP_GROUP = new NioEventLoopGroup( 4, new ThreadFactoryBuilder()
.setDaemon( true )
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
.setNameFormat( "ComputerCraft-Netty-%d" )
.build()
);

private HTTPExecutor()
{
}
}

0 comments on commit c9181a1

Please sign in to comment.