Browse Source

Adding the Web module with WebSockets working.
No Emscripten support just yet.
Also, WebRequest is still a work in progress.

Jay Sistar 10 years ago
parent
commit
73e8d960ce

+ 2 - 0
CMakeLists.txt

@@ -36,6 +36,8 @@ if (NOT EMSCRIPTEN)
     set (ATOMIC_LINK_LIBRARIES ${ATOMIC_LINK_LIBRARIES} SDL Civetweb Recast Detour DetourCrowd DetourTileCache kNet )
 endif()
 
+add_definitions( -DATOMIC_WEB )
+
 if (MSVC)
 
     include(AtomicWindows)

+ 1 - 1
Script/Packages/Atomic/Package.json

@@ -5,7 +5,7 @@
 
 	"modules" : ["Container", "Math", "Core", "Scene", "Graphics", "Atomic3D", "Atomic2D", "Audio",
 	"Physics", "Navigation", "Input", "UI", "Resource", "Network", "IO",
-	"Engine", "Javascript", "Environment"],
+	"Engine", "Javascript", "Environment", "Web"],
 
 	"moduleExclude" : {
 		"WEB" : ["Network", "Navigation"]

+ 5 - 0
Script/Packages/Atomic/Web.json

@@ -0,0 +1,5 @@
+{
+	"name" : "Web",
+	"sources" : ["Source/Atomic/Web"],
+	"classes" : ["Web", "WebRequest", "WebSocket"]
+}

+ 7 - 2
Source/Atomic/CMakeLists.txt

@@ -1,10 +1,13 @@
 
 include_directories(${CMAKE_CURRENT_SOURCE_DIR}
                     ${CMAKE_SOURCE_DIR}/Source/ThirdParty
+                    ${CMAKE_SOURCE_DIR}/Source/ThirdParty/WebSocketPP/include
                     ${CMAKE_SOURCE_DIR}/Source/ThirdParty/rapidjson/include
+                    ${CMAKE_SOURCE_DIR}/Source/ThirdParty/libcurl/include
                     ${CMAKE_SOURCE_DIR}/Source/ThirdParty/kNet/include
                     ${CMAKE_SOURCE_DIR}/Source/ThirdParty/FreeType/include
-                    ${CMAKE_SOURCE_DIR}/Source/ThirdParty/Box2D)
+                    ${CMAKE_SOURCE_DIR}/Source/ThirdParty/Box2D
+                    ${CMAKE_SOURCE_DIR}/Source/ThirdParty/ASIO/include)
 
 file (GLOB CONTAINER_SOURCE Container/*.cpp Container/*.h)
 file (GLOB CORE_SOURCE Core/*.cpp Core/*.h)
@@ -15,6 +18,7 @@ file (GLOB IO_SOURCE IO/*.cpp IO/*.h)
 file (GLOB RESOURCE_SOURCE Resource/*.cpp Resource/*.h)
 file (GLOB AUDIO_SOURCE Audio/*.cpp Audio/*.h)
 file (GLOB NETWORK_SOURCE Network/*.cpp Network/*.h)
+file (GLOB WEB_SOURCE Web/*.cpp Web/*.h)
 
 if (NOT EMSCRIPTEN AND NOT IOS AND NOT ANDROID)
   file (GLOB IPC_SOURCE IPC/*.cpp IPC/*.h)
@@ -68,7 +72,7 @@ set (SOURCE_FILES ${CONTAINER_SOURCE} ${CORE_SOURCE} ${ENGINE_SOURCE} ${INPUT_SO
                   ${GRAPHICS_SOURCE} ${GRAPHICS_IMPL_SOURCE}
                   ${ATOMIC3D_SOURCE}
                   ${ATOMIC2D_SOURCE} ${ENVIRONMENT_SOURCE}
-                  ${SCENE_SOURCE} ${UI_SOURCE} ${SYSTEM_UI_SOURCE}
+                  ${SCENE_SOURCE} ${UI_SOURCE} ${SYSTEM_UI_SOURCE} ${WEB_SOURCE}
                   ${PLATFORM_SOURCE})
 
 if (NOT EMSCRIPTEN)
@@ -93,5 +97,6 @@ GroupSources("Physics")
 GroupSources("Resource")
 GroupSources("Scene")
 GroupSources("UI")
+GroupSources("Web")
 
 add_library(Atomic ${SOURCE_FILES})

+ 6 - 0
Source/Atomic/Engine/Engine.cpp

@@ -41,6 +41,9 @@
 #ifdef ATOMIC_NETWORK
 #include "../Network/Network.h"
 #endif
+#ifdef ATOMIC_WEB
+#include "../Web/Web.h"
+#endif
 #ifdef ATOMIC_DATABASE
 #include "../Database/Database.h"
 #endif
@@ -130,6 +133,9 @@ Engine::Engine(Context* context) :
 #ifdef ATOMIC_NETWORK
     context_->RegisterSubsystem(new Network(context_));
 #endif
+#ifdef ATOMIC_WEB
+    context_->RegisterSubsystem(new Web(context_));
+#endif
 #ifdef ATOMIC_DATABASE
     context_->RegisterSubsystem(new Database(context_));
 #endif

+ 91 - 0
Source/Atomic/Web/Web.cpp

@@ -0,0 +1,91 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../Precompiled.h"
+
+#include "../Core/Profiler.h"
+#include "../Core/CoreEvents.h"
+#include "../IO/Log.h"
+#include "../Web/Web.h"
+#include "../Web/WebRequest.h"
+#include "../Web/WebSocket.h"
+
+#ifndef EMSCRIPTEN
+// !!! Should go in an ASIOConfig.h file.
+#define ASIO_STANDALONE
+#define ASIO_HAS_STD_ARRAY
+#define ASIO_HAS_CSTDINT
+#include <asio/io_service.hpp>
+#endif
+
+#include "../DebugNew.h"
+
+namespace Atomic
+{
+
+struct WebPrivate
+{
+#ifndef EMSCRIPTEN
+    asio::io_service service;
+#endif
+};
+
+Web::Web(Context* context) :
+    Object(context),
+    d(new WebPrivate())
+{
+    SubscribeToEvent(E_UPDATE, HANDLER(Web, internalUpdate));
+}
+
+Web::~Web()
+{
+    UnsubscribeFromEvent(E_UPDATE);
+    delete d;
+}
+
+void Web::internalUpdate(StringHash eventType, VariantMap& eventData)
+{
+    d->service.reset();
+    d->service.poll();
+}
+
+SharedPtr<WebRequest> Web::MakeWebRequest(const String& url, const String& verb, const Vector<String>& headers,
+    const String& postData)
+{
+    PROFILE(MakeWebRequest);
+
+    // The initialization of the request will take time, can not know at this point if it has an error or not
+    SharedPtr<WebRequest> request(new WebRequest(url, verb, headers, postData));
+    return request;
+}
+
+SharedPtr<WebSocket> Web::MakeWebSocket(const String& url)
+{
+  PROFILE(MakeWebSocket);
+
+  // The initialization of the WebSocket will take time, can not know at this point if it has an error or not
+  SharedPtr<WebSocket> webSocket(new WebSocket(context_, url));
+  webSocket->setup(&d->service);
+  return webSocket;
+}
+
+}

+ 59 - 0
Source/Atomic/Web/Web.h

@@ -0,0 +1,59 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Core/Object.h"
+#include "../IO/VectorBuffer.h"
+
+namespace Atomic
+{
+
+class WebRequest;
+class WebSocket;
+
+class WebPrivate;
+
+/// %Web subsystem. Manages HTTP requests and WebSocket communications.
+class ATOMIC_API Web : public Object
+{
+    OBJECT(Web);
+
+public:
+    /// Construct.
+    Web(Context* context);
+    /// Destruct.
+    ~Web();
+
+    /// Perform an HTTP request to the specified URL. Empty verb defaults to a GET request. Return a request object which can be used to read the response data.
+    SharedPtr<WebRequest> MakeWebRequest
+        (const String& url, const String& verb = String::EMPTY, const Vector<String>& headers = Vector<String>(),
+            const String& postData = String::EMPTY);
+    /// Perform an WebSocket request to the specified URL. Return a WebSocket object which can be used to comunicate with the server.
+    SharedPtr<WebSocket> MakeWebSocket(const String& url);
+
+private:
+    void internalUpdate(StringHash eventType, VariantMap& eventData);
+    WebPrivate *d;
+};
+
+}

+ 35 - 0
Source/Atomic/Web/WebInternalConfig.h

@@ -0,0 +1,35 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#define _WEBSOCKETPP_CPP11_STRICT_
+#define WEBSOCKETPP_STRICT_MASKING
+#ifdef _MSC_VER
+#  if (_MSC_VER <= 1800)
+#    define _WEBSOCKETPP_NOEXCEPT_TOKEN_
+#    define _WEBSOCKETPP_CONSTEXPR_TOKEN_
+#  endif
+#endif
+#define ASIO_STANDALONE
+#define ASIO_HAS_STD_ARRAY
+#define ASIO_HAS_CSTDINT

+ 301 - 0
Source/Atomic/Web/WebRequest.cpp

@@ -0,0 +1,301 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../Precompiled.h"
+
+#include "../Core/Profiler.h"
+#include "../IO/Log.h"
+#include "../Web/WebRequest.h"
+
+#include "../DebugNew.h"
+
+// !!! WARNING! Use HttpRequest from Network if you don't want your interface
+//              to change. This file mimics HttpRequest for now, but will be
+//              changing, and it is here as a placeholder only!
+
+#ifdef EMSCRIPTEN
+
+// Add code to use an XMLHttpRequest or ActiveX XMLHttpRequest here.
+
+#else
+
+#include <Civetweb/include/civetweb.h>
+
+
+namespace Atomic
+{
+
+static const unsigned ERROR_BUFFER_SIZE = 256;
+static const unsigned READ_BUFFER_SIZE = 65536; // Must be a power of two
+
+WebRequest::WebRequest(const String& url, const String& verb, const Vector<String>& headers, const String& postData) :
+    url_(url.Trimmed()),
+    verb_(!verb.Empty() ? verb : "GET"),
+    headers_(headers),
+    postData_(postData),
+    state_(HTTP_INITIALIZING),
+    httpReadBuffer_(new unsigned char[READ_BUFFER_SIZE]),
+    readBuffer_(new unsigned char[READ_BUFFER_SIZE]),
+    readPosition_(0),
+    writePosition_(0)
+{
+    // Size of response is unknown, so just set maximum value. The position will also be changed
+    // to maximum value once the request is done, signaling end for Deserializer::IsEof().
+    size_ = M_MAX_UNSIGNED;
+
+    LOGERROR("DO NOT USE WebRequest YET! Use the Network subsystem's HttpRequest. The WebRequest interface is under development and will change soon!");
+    LOGDEBUG("HTTP " + verb_ + " request to URL " + url_);
+
+    // Start the worker thread to actually create the connection and read the response data.
+    Run();
+}
+
+WebRequest::~WebRequest()
+{
+    Stop();
+}
+
+void WebRequest::ThreadFunction()
+{
+    String protocol = "http";
+    String host;
+    String path = "/";
+    int port = 80;
+
+    unsigned protocolEnd = url_.Find("://");
+    if (protocolEnd != String::NPOS)
+    {
+        protocol = url_.Substring(0, protocolEnd);
+        host = url_.Substring(protocolEnd + 3);
+    }
+    else
+        host = url_;
+
+    unsigned pathStart = host.Find('/');
+    if (pathStart != String::NPOS)
+    {
+        path = host.Substring(pathStart);
+        host = host.Substring(0, pathStart);
+    }
+
+    unsigned portStart = host.Find(':');
+    if (portStart != String::NPOS)
+    {
+        port = ToInt(host.Substring(portStart + 1));
+        host = host.Substring(0, portStart);
+    }
+
+    char errorBuffer[ERROR_BUFFER_SIZE];
+    memset(errorBuffer, 0, sizeof(errorBuffer));
+
+    String headersStr;
+    for (unsigned i = 0; i < headers_.Size(); ++i)
+    {
+        // Trim and only add non-empty header strings
+        String header = headers_[i].Trimmed();
+        if (header.Length())
+            headersStr += header + "\r\n";
+    }
+
+    // Initiate the connection. This may block due to DNS query
+    /// \todo SSL mode will not actually work unless Civetweb's SSL mode is initialized with an external SSL DLL
+    mg_connection* connection = 0;
+    if (postData_.Empty())
+    {
+        connection = mg_download(host.CString(), port, protocol.Compare("https", false) ? 0 : 1, errorBuffer, sizeof(errorBuffer),
+            "%s %s HTTP/1.1\r\n"
+            "Host: %s\r\n"
+            "Connection: close\r\n"
+            "%s"
+            "\r\n", verb_.CString(), path.CString(), host.CString(), headersStr.CString());
+    }
+    else
+    {
+        connection = mg_download(host.CString(), port, protocol.Compare("https", false) ? 0 : 1, errorBuffer, sizeof(errorBuffer),
+            "%s %s HTTP/1.1\r\n"
+            "Host: %s\r\n"
+            "Connection: close\r\n"
+            "%s"
+            "Content-Length: %d\r\n"
+            "\r\n"
+            "%s", verb_.CString(), path.CString(), host.CString(), headersStr.CString(), postData_.Length(), postData_.CString());
+    }
+
+    {
+        MutexLock lock(mutex_);
+        state_ = connection ? HTTP_OPEN : HTTP_ERROR;
+
+        // If no connection could be made, store the error and exit
+        if (state_ == HTTP_ERROR)
+        {
+            error_ = String(&errorBuffer[0]);
+            return;
+        }
+    }
+
+    // Loop while should run, read data from the connection, copy to the main thread buffer if there is space
+    while (shouldRun_)
+    {
+        // Read less than full buffer to be able to distinguish between full and empty ring buffer. Reading may block
+        int bytesRead = mg_read(connection, httpReadBuffer_.Get(), READ_BUFFER_SIZE / 4);
+        if (bytesRead <= 0)
+            break;
+
+        mutex_.Acquire();
+
+        // Wait until enough space in the main thread's ring buffer
+        for (;;)
+        {
+            unsigned spaceInBuffer = READ_BUFFER_SIZE - ((writePosition_ - readPosition_) & (READ_BUFFER_SIZE - 1));
+            if ((int)spaceInBuffer > bytesRead || !shouldRun_)
+                break;
+
+            mutex_.Release();
+            Time::Sleep(5);
+            mutex_.Acquire();
+        }
+
+        if (!shouldRun_)
+        {
+            mutex_.Release();
+            break;
+        }
+
+        if (writePosition_ + bytesRead <= READ_BUFFER_SIZE)
+            memcpy(readBuffer_.Get() + writePosition_, httpReadBuffer_.Get(), (size_t)bytesRead);
+        else
+        {
+            // Handle ring buffer wrap
+            unsigned part1 = READ_BUFFER_SIZE - writePosition_;
+            unsigned part2 = bytesRead - part1;
+            memcpy(readBuffer_.Get() + writePosition_, httpReadBuffer_.Get(), part1);
+            memcpy(readBuffer_.Get(), httpReadBuffer_.Get() + part1, part2);
+        }
+
+        writePosition_ += bytesRead;
+        writePosition_ &= READ_BUFFER_SIZE - 1;
+
+        mutex_.Release();
+    }
+
+    // Close the connection
+    mg_close_connection(connection);
+
+    {
+        MutexLock lock(mutex_);
+        state_ = HTTP_CLOSED;
+    }
+}
+
+unsigned WebRequest::Read(void* dest, unsigned size)
+{
+    mutex_.Acquire();
+
+    unsigned char* destPtr = (unsigned char*)dest;
+    unsigned sizeLeft = size;
+    unsigned totalRead = 0;
+
+    for (;;)
+    {
+        unsigned bytesAvailable;
+
+        for (;;)
+        {
+            bytesAvailable = CheckEofAndAvailableSize();
+            if (bytesAvailable || IsEof())
+                break;
+            // While no bytes and connection is still open, block until has some data
+            mutex_.Release();
+            Time::Sleep(5);
+            mutex_.Acquire();
+        }
+
+        if (bytesAvailable)
+        {
+            if (bytesAvailable > sizeLeft)
+                bytesAvailable = sizeLeft;
+
+            if (readPosition_ + bytesAvailable <= READ_BUFFER_SIZE)
+                memcpy(destPtr, readBuffer_.Get() + readPosition_, bytesAvailable);
+            else
+            {
+                // Handle ring buffer wrap
+                unsigned part1 = READ_BUFFER_SIZE - readPosition_;
+                unsigned part2 = bytesAvailable - part1;
+                memcpy(destPtr, readBuffer_.Get() + readPosition_, part1);
+                memcpy(destPtr + part1, readBuffer_.Get(), part2);
+            }
+
+            readPosition_ += bytesAvailable;
+            readPosition_ &= READ_BUFFER_SIZE - 1;
+            sizeLeft -= bytesAvailable;
+            totalRead += bytesAvailable;
+            destPtr += bytesAvailable;
+        }
+
+        if (!sizeLeft || !bytesAvailable)
+            break;
+    }
+
+    // Check for end-of-file once more after reading the bytes
+    CheckEofAndAvailableSize();
+    mutex_.Release();
+    return totalRead;
+}
+
+unsigned WebRequest::Seek(unsigned position)
+{
+    return position_;
+}
+
+String WebRequest::GetError() const
+{
+    MutexLock lock(mutex_);
+    const_cast<WebRequest*>(this)->CheckEofAndAvailableSize();
+    return error_;
+}
+
+WebRequestState WebRequest::GetState() const
+{
+    MutexLock lock(mutex_);
+    const_cast<WebRequest*>(this)->CheckEofAndAvailableSize();
+    return state_;
+}
+
+unsigned WebRequest::GetAvailableSize() const
+{
+    MutexLock lock(mutex_);
+    return const_cast<WebRequest*>(this)->CheckEofAndAvailableSize();
+}
+
+unsigned WebRequest::CheckEofAndAvailableSize()
+{
+    unsigned bytesAvailable = (writePosition_ - readPosition_) & (READ_BUFFER_SIZE - 1);
+    if (state_ == HTTP_ERROR || (state_ == HTTP_CLOSED && !bytesAvailable))
+        position_ = M_MAX_UNSIGNED;
+    return bytesAvailable;
+}
+
+}
+
+#endif

+ 106 - 0
Source/Atomic/Web/WebRequest.h

@@ -0,0 +1,106 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Container/ArrayPtr.h"
+#include "../Core/Mutex.h"
+#include "../Container/RefCounted.h"
+#include "../Core/Thread.h"
+#include "../IO/Deserializer.h"
+
+namespace Atomic
+{
+
+/// HTTP connection state
+enum WebRequestState
+{
+    HTTP_INITIALIZING = 0,
+    HTTP_ERROR,
+    HTTP_OPEN,
+    HTTP_CLOSED
+};
+
+/// An HTTP connection with response data stream.
+class WebRequest : public RefCounted, public Deserializer, public Thread
+{
+    REFCOUNTED(WebRequest)
+
+public:
+    /// Construct with parameters.
+    WebRequest(const String& url, const String& verb, const Vector<String>& headers, const String& postData);
+    /// Destruct. Release the connection object.
+    ~WebRequest();
+
+    /// Process the connection in the worker thread until closed.
+    virtual void ThreadFunction();
+
+    /// Read response data from the HTTP connection and return number of bytes actually read. While the connection is open, will block while trying to read the specified size. To avoid blocking, only read up to as many bytes as GetAvailableSize() returns.
+    virtual unsigned Read(void* dest, unsigned size);
+    /// Set position from the beginning of the stream. Not supported.
+    virtual unsigned Seek(unsigned position);
+
+    /// Return URL used in the request.
+    const String& GetURL() const { return url_; }
+
+    /// Return verb used in the request. Default GET if empty verb specified on construction.
+    const String& GetVerb() const { return verb_; }
+
+    /// Return error. Only non-empty in the error state.
+    String GetError() const;
+    /// Return connection state.
+    WebRequestState GetState() const;
+    /// Return amount of bytes in the read buffer.
+    unsigned GetAvailableSize() const;
+
+    /// Return whether connection is in the open state.
+    bool IsOpen() const { return GetState() == HTTP_OPEN; }
+
+private:
+    /// Check for end of the data stream and return available size in buffer. Must only be called when the mutex is held by the main thread.
+    unsigned CheckEofAndAvailableSize();
+
+    /// URL.
+    String url_;
+    /// Verb.
+    String verb_;
+    /// Error string. Empty if no error.
+    String error_;
+    /// Headers.
+    Vector<String> headers_;
+    /// POST data.
+    String postData_;
+    /// Connection state.
+    WebRequestState state_;
+    /// Mutex for synchronizing the worker and the main thread.
+    mutable Mutex mutex_;
+    /// Read buffer for the worker thread.
+    SharedArrayPtr<unsigned char> httpReadBuffer_;
+    /// Read buffer for the main thread.
+    SharedArrayPtr<unsigned char> readBuffer_;
+    /// Read buffer read cursor.
+    unsigned readPosition_;
+    /// Read buffer write cursor.
+    unsigned writePosition_;
+};
+
+}

+ 224 - 0
Source/Atomic/Web/WebSocket.cpp

@@ -0,0 +1,224 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../Precompiled.h"
+
+#include "../Core/Profiler.h"
+#include "../IO/Log.h"
+#include "../Web/WebSocket.h"
+
+#ifdef EMSCRIPTEN
+
+#include "../DebugNew.h"
+
+// Add code to use an HTML5 style WebSocket here.
+
+#else
+
+#include "../Web/WebInternalConfig.h"
+
+#include <websocketpp/config/asio_no_tls_client.hpp>
+
+#include <websocketpp/client.hpp>
+
+#include <iostream>
+
+#include "../Core/Thread.h"
+
+#include "../DebugNew.h"
+
+typedef websocketpp::client<websocketpp::config::asio_client> client;
+typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
+
+using websocketpp::lib::placeholders::_1;
+using websocketpp::lib::placeholders::_2;
+using websocketpp::lib::bind;
+
+
+namespace Atomic
+{
+
+struct WebSocketInternalState
+{
+  /// The work queue.
+  asio::io_service service;
+  /// The WebSocket external state.
+  WebSocket &es;
+  /// URL.
+  String url;
+  /// Error string. Empty if no error.
+  String error;
+  /// Connection state.
+  WebSocketState state;
+  /// WebSocket client.
+  client c;
+  /// WebSocket connection.
+  client::connection_ptr con;
+
+  WebSocketInternalState(WebSocket &es_)
+    : es(es_)
+  {
+  }
+
+  ~WebSocketInternalState()
+  {
+  }
+
+  void OnOpen(websocketpp::connection_hdl hdl)
+  {
+    state = WS_OPEN;
+    LOGDEBUG("WebSocket CONNECTED to: " + url);
+    es.SendEvent("open");
+  }
+
+  void OnClose(websocketpp::connection_hdl hdl)
+  {
+    state = WS_CLOSED;
+    LOGDEBUG("WebSocket DISCONNECTED from: " + url);
+    es.SendEvent("close");
+  }
+
+  void OnFail(websocketpp::connection_hdl hdl)
+  {
+    state = WS_FAIL_TO_CONNECT;
+    LOGDEBUG("WebSocket FAILED to connect to: " + url);
+    es.SendEvent("fail_to_connect");
+  }
+
+  void OnMessage(websocketpp::connection_hdl hdl, message_ptr msg)
+  {
+    VariantMap eventData;
+    const std::string &payload(msg->get_payload());
+
+    eventData.Insert(MakePair(StringHash("type"), Variant(int(msg->get_opcode()))));
+
+    switch (msg->get_opcode())
+    {
+      case websocketpp::frame::opcode::text:
+        eventData.Insert(MakePair(StringHash("data"), Variant(String(payload.data(), payload.length()))));
+        es.SendEvent("message", eventData);
+        break;
+
+      case websocketpp::frame::opcode::binary:
+        eventData.Insert(MakePair(StringHash("data"), Variant(PODVector<unsigned char>((const unsigned char *)payload.data(), payload.length()))));
+        es.SendEvent("message", eventData);
+        break;
+
+      default:
+        eventData.Insert(MakePair(StringHash("data"), Variant(String(payload.data(), payload.length()))));
+        LOGWARNING("Unsupported WebSocket message type: " + String(int(msg->get_opcode())));
+        break;
+    }
+  }
+
+  void MakeConnection()
+  {
+    websocketpp::lib::error_code ec;
+    con = c.get_connection(url.CString(), ec);
+    if (ec)
+    {
+      state = WS_INVALID;
+      error = ec.message().c_str();
+      LOGDEBUG("WebSocket error: " + error);
+      es.SendEvent("invalid");
+      return;
+    }
+    c.connect(con);
+  }
+};
+
+WebSocket::WebSocket(Context* context, const String& url) :
+  Object(context),
+  is_(new WebSocketInternalState(*this))
+{
+  is_->url = url.Trimmed();
+  is_->state = WS_CONNECTING;
+
+  is_->c.clear_access_channels(websocketpp::log::alevel::all);
+  is_->c.clear_error_channels(websocketpp::log::elevel::all);
+}
+
+void WebSocket::setup(asio::io_service *service)
+{
+  websocketpp::lib::error_code ec;
+  is_->c.init_asio(service, ec);
+  if (ec)
+  {
+    is_->state = WS_INVALID;
+    is_->error = ec.message().c_str();
+    LOGDEBUG("WebSocket error: " + is_->error);
+    SendEvent("invalid");
+    return;
+  }
+  is_->c.set_open_handler(bind(&WebSocketInternalState::OnOpen, is_, ::_1));
+  is_->c.set_close_handler(bind(&WebSocketInternalState::OnClose, is_, ::_1));
+  is_->c.set_fail_handler(bind(&WebSocketInternalState::OnFail, is_, ::_1));
+  is_->c.set_message_handler(bind(&WebSocketInternalState::OnMessage, is_, ::_1, ::_2));
+
+  LOGDEBUG("WebSocket request to URL " + is_->url);
+
+  is_->MakeConnection();
+}
+
+WebSocket::~WebSocket()
+{
+  delete is_;
+}
+
+const String& WebSocket::GetURL() const
+{
+  return is_->url;
+}
+
+String WebSocket::GetError() const
+{
+  return is_->error;
+}
+
+WebSocketState WebSocket::GetState() const
+{
+  return is_->state;
+}
+
+void WebSocket::Send(String message)
+{
+  is_->c.send(is_->con, message.CString(), message.Length(), websocketpp::frame::opcode::text);
+}
+
+void  WebSocket::Close()
+{
+  is_->state = WS_CLOSING;
+  websocketpp::lib::error_code ec;
+  LOGDEBUG("WebSocket atempting to close URL " + is_->url);
+  is_->con->terminate(ec);
+}
+
+void WebSocket::OpenAgain()
+{
+  is_->state = WS_CONNECTING;
+  LOGDEBUG("WebSocket request (again) to URL " + is_->url);
+  is_->MakeConnection();
+}
+
+}
+
+#endif

+ 88 - 0
Source/Atomic/Web/WebSocket.h

@@ -0,0 +1,88 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Core/Object.h"
+#include "../Container/Str.h"
+#include "../IO/Deserializer.h"
+
+namespace asio
+{
+class io_service;
+}
+
+namespace Atomic
+{
+
+/// WebSocket connection state
+enum WebSocketState
+{
+    WS_CONNECTING = 0,   // Establishing connection
+    WS_OPEN,             // WebSocket is open
+    WS_CLOSING,          // WebSocket is being closed (and is probably still open)
+    WS_CLOSED,           // WebSocket is closed or was disconnected
+    WS_INVALID,          // Invalid state
+    WS_FAIL_TO_CONNECT   // WebSocket attempted to open, but the server refused
+};
+
+struct WebSocketInternalState;
+
+/// A WebSocket connection.
+class WebSocket : public Object
+{
+    friend class Web;
+
+    OBJECT(WebSocket)
+
+public:
+    /// Construct with parameters.
+    WebSocket(Context* context, const String& url);
+    /// Destruct. Release the connection object.
+    ~WebSocket();
+
+    /// Return URL used in the request.
+    const String& GetURL() const;
+
+    /// Return error. Only non-empty in the error state.
+    String GetError() const;
+    /// Return connection state.
+    WebSocketState GetState() const;
+
+    /// Send a message.
+    void Send(String message);
+
+    /// Disconnect the WebSocket.
+    void Close();
+
+    /// Attempt to reconnect the WebSocket.
+    void OpenAgain();
+
+    /// Return whether connection is in the open state.
+    bool IsOpen() const { return GetState() == WS_OPEN; }
+
+private:
+    void setup(asio::io_service *service);
+    WebSocketInternalState* is_;
+};
+
+}

+ 1 - 1
Source/AtomicJS/CMakeLists.txt

@@ -20,7 +20,7 @@ set (JSFILES JSModuleAtomic2D.cpp;JSModuleAtomic3D.cpp;JSModuleAudio.cpp;JSModul
              JSModuleEngine.cpp;JSModuleEnvironment.cpp;JSModuleGraphics.cpp;JSModuleInput.cpp;
              JSModuleIO.cpp;JSModuleJavascript.cpp;JSModuleMath.cpp;JSModuleNavigation.cpp;
              JSModuleNetwork.cpp;JSModulePhysics.cpp;JSModuleResource.cpp;JSPackageAtomic.cpp;
-             JSModuleScene.cpp;JSModuleUI.cpp)
+             JSModuleScene.cpp;JSModuleUI.cpp;JSModuleWeb.cpp)
 
 foreach(JSFILE ${JSFILES})
 

+ 21 - 0
Source/AtomicJS/Javascript/JSAtomic.cpp

@@ -35,6 +35,10 @@
 #include <Atomic/Network/Network.h>
 #endif
 
+#ifdef ATOMIC_WEB
+#include <Atomic/Web/Web.h>
+#endif
+
 #include "JSEvents.h"
 #include "JSVM.h"
 #include "JSComponent.h"
@@ -177,6 +181,15 @@ static int js_atomic_GetNetwork(duk_context* ctx)
 }
 #endif
 
+#ifdef ATOMIC_WEB
+static int js_atomic_GetWeb(duk_context* ctx)
+{
+  JSVM* vm = JSVM::GetJSVM(ctx);
+  js_push_class_object_instance(ctx, vm->GetSubsystem<Web>());
+  return 1;
+}
+#endif
+
 static int js_atomic_GetUI(duk_context* ctx)
 {
     JSVM* vm = JSVM::GetJSVM(ctx);
@@ -395,6 +408,14 @@ void jsapi_init_atomic(JSVM* vm)
     duk_put_prop_string(ctx, -2, "network");
 #endif
 
+#ifdef ATOMIC_WEB
+    duk_push_c_function(ctx, js_atomic_GetWeb, 0);
+    duk_put_prop_string(ctx, -2, "getWeb");
+
+    js_push_class_object_instance(ctx, vm->GetSubsystem<Web>(), "Web");
+    duk_put_prop_string(ctx, -2, "web");
+#endif
+
     duk_push_c_function(ctx, js_atomic_GetUI, 0);
     duk_put_prop_string(ctx, -2, "getUI");