Преглед на файлове

Adding HTTPFetcher to fetch files from the WWW via HTTP, fixing getThreadID on Windows.

Joachim Meyer преди 10 години
родител
ревизия
28471b9e2b

+ 2 - 0
Core/Contents/CMakeLists.txt

@@ -80,6 +80,7 @@ SET(polycore_SRCS
     Source/PolyPeer.cpp
     Source/PolyClient.cpp
     Source/PolyServer.cpp
+	Source/PolyHTTPFetcher.cpp
     Source/PolyRay.cpp
     Source/PolySceneSprite.cpp
     Source/PolySceneEntityInstance.cpp
@@ -169,6 +170,7 @@ SET(polycore_HDRS
     Include/PolyClient.h
     Include/PolyServer.h
     Include/PolyServerWorld.h
+	Include/PolyHTTPFetcher.h
     Include/PolyRay.h
     Include/PolySceneSprite.h
     Include/PolySceneEntityInstance.h

+ 97 - 0
Core/Contents/Include/PolyHTTPFetcher.h

@@ -0,0 +1,97 @@
+/*
+Copyright (C) 2015 by Joachim Meyer
+
+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 <string.h>
+
+#include "PolyGlobals.h"
+#include "PolyThreaded.h"
+
+#define HTTP_VERSION 			"HTTP/1.1"
+#define DEFAULT_USER_AGENT		"Polycode HTTP Fetcher/1.0"
+#define DEFAULT_PAGE_BUF_SIZE 	2048
+
+namespace Polycode {
+	
+	class HTTPFetcherEvent : public Event {
+	public:
+		HTTPFetcherEvent() { contentSize = 0; errorCode = 0; data = NULL; storedInFile = false; }
+		~HTTPFetcherEvent(){}
+
+		//If storedInFile: data is the file path, else: data contains all the fetched data
+		char* data;
+		//Error code: contains either the errno / WSAError code or the HTTP error code or the HTTPFetcher error code
+		int errorCode;
+
+		//Has the data been saved to a file or is it shipped with this event?
+		bool storedInFile;
+		//Size of the HTTP reply
+		unsigned long contentSize;
+
+		static const int EVENTBASE_SOCKETEVENT = 0x500;
+		static const int EVENT_HTTP_ERROR = EVENTBASE_SOCKETEVENT + 2;
+		static const int EVENT_HTTP_DATA_RECEIVED = EVENTBASE_SOCKETEVENT + 3;
+	};
+
+	/**
+	* A utility to download a file from the WWW through HTTP. It is threaded (and therefor non blocking).
+	* If you want to use the data you might add an EventListener for the HTTPFetcherEvent::EVENT_HTTP_DATA_RECEIVED event code.
+	*/
+	class HTTPFetcher : public Threaded {
+	public:
+		/*
+		* Connects to a host and fetches a file given in the param
+		* @param address Full path including the hostname (Domain or IP) and protocol (http://) aswell as the path to the file on the server
+		* @param saveToPath true if you want the file to be directly saved, false if you just want the data as char array
+		* @param savePath Path String where the file should be saved to
+		*/
+		HTTPFetcher(String address, bool saveToPath = false, String savePath = "");
+		~HTTPFetcher();
+
+		String getData();
+
+		/*
+		* Fetches a file given in the param
+		* @param pathToFile Path String to the new file to fetch from the same host. Without leading "/"
+		* @param saveToPath true if you want the file to be directly saved, false if you just want the data as char array
+		* @param savePath Path String where the file should be saved to
+		*/
+		void fetchFile(String pathToFile, bool saveToPath = false, String savePath = "");
+
+		//The received data is more or less than the HTTP header told us it should be
+		static const int HTTPFETCHER_ERROR_WRONG_SIZE = 0x10F00;
+
+		bool storeInFile;
+
+	private:
+        int s;
+		String address;
+		String bodyReturn;
+		String path;
+		String host;
+		String protocol;
+		String savePath;
+
+		bool createSocket();
+		void updateThread();
+	};
+}

+ 1 - 0
Core/Contents/Include/Polycode.h

@@ -86,6 +86,7 @@
 #include "PolyServer.h"
 #include "PolyServerWorld.h"
 #include "PolySocket.h"
+#include "PolyHTTPFetcher.h"
 #include "PolyRay.h"
 #include "PolySceneSprite.h"
 #include "PolySceneEntityInstance.h"

+ 294 - 0
Core/Contents/Source/PolyHTTPFetcher.cpp

@@ -0,0 +1,294 @@
+/*
+Copyright (C) 2015 by Joachim Meyer
+
+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.
+*/
+
+#ifdef _WINDOWS
+#include <winsock2.h>
+#include <Ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#endif
+
+#include "PolyHTTPFetcher.h"
+#include "PolyLogger.h"
+#include "PolyCoreServices.h"
+#include "PolyCore.h"
+
+using namespace Polycode;
+
+HTTPFetcher::HTTPFetcher(String address, bool saveToPath, String savePath) : Threaded() {
+	core = CoreServices::getInstance()->getCore();
+	eventMutex = core->getEventMutex();
+
+	storeInFile = saveToPath;
+	this->savePath = savePath;
+
+	this->address = address;
+	int protocolIndex = address.find_first_of("://");
+    if (protocolIndex != 0){
+		protocolIndex += strlen("://");
+		protocol = address.substr(0, protocolIndex - strlen("://"));
+		int pathIndex = address.find_first_of("/", protocolIndex);
+		path = address.substr(pathIndex+1, address.length());
+		
+		if (pathIndex != 0){
+			host = address.substr(protocolIndex, pathIndex - protocolIndex);
+		} else {
+			host = address.substr(protocolIndex, address.length());
+		}
+	} else {
+		int pathIndex = address.find_first_of("/");
+		path = address.substr(pathIndex+1, address.length());
+
+		if (pathIndex != 0){
+			host = address.substr(0, pathIndex);
+		} else {
+			host = address;
+		}
+	}
+
+	if (!createSocket())
+		return;
+
+	threadRunning = true;
+	CoreServices::getInstance()->getCore()->createThread(this);
+}
+
+HTTPFetcher::~HTTPFetcher(){
+#ifdef _WINDOWS
+	closesocket(s);
+#else
+	close(s);
+#endif
+}
+
+bool HTTPFetcher::createSocket(){
+	struct sockaddr_in server;
+
+	addrinfo *result = NULL;
+	addrinfo hints;
+
+	//Create a socket
+#if PLATFORM == PLATFORM_WINDOWS
+	if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
+		Logger::log("HTTP Fetcher: Could not create socket: %d\n", WSAGetLastError());
+#elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
+	if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
+		Logger::log("HTTP Fetcher: Could not create socket: %s\n", strerror(errno));
+#endif
+		return false;
+	}
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+
+	if (getaddrinfo(host.c_str(), protocol.c_str(), &hints, &result) != 0) {
+#if PLATFORM == PLATFORM_WINDOWS
+		Logger::log("HTTP Fetcher: Address resolve error: %d\n", WSAGetLastError());
+#elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
+		Logger::log("HTTP Fetcher: Address resolve error: %s\n", strerror(errno));
+#endif
+		return false;
+	}
+
+	server.sin_addr = ((sockaddr_in*)result->ai_addr)->sin_addr;
+	server.sin_family = AF_INET;
+	server.sin_port = ((sockaddr_in*)result->ai_addr)->sin_port;
+
+	//Connect to remote server
+	if (connect(s, (struct sockaddr *)&server, sizeof(server)) < 0) {
+#if PLATFORM == PLATFORM_WINDOWS
+		Logger::log("HTTP Fetcher: connect error code: %d\n", WSAGetLastError());
+#elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
+		Logger::log("HTTP Fetcher: connect error code: %s\n", strerror(errno));
+#endif
+		return false;
+	}
+	return true;
+}
+
+void HTTPFetcher::updateThread(){
+	int protocolIndex = path.find_first_of("://");
+	if (protocolIndex != 0){
+		protocolIndex += strlen("://");
+		protocol = path.substr(0, protocolIndex - strlen("://"));
+		int pathIndex = path.find_first_of("/", protocolIndex);
+		path = path.substr(pathIndex + 1, path.length());
+	} else if (path.find_first_of("/") == 0) {
+		path = path.substr(1, path.length());
+	}
+
+	//Send some data
+	String request;
+	if (path != "") {
+		request = "GET /" + path + " " + String(HTTP_VERSION) + "\r\nHost: " + host + "\r\nUser-Agent: " + DEFAULT_USER_AGENT + "\r\nConnection: close\r\n\r\n";
+	} else {
+		request = "GET / " + String(HTTP_VERSION) + "\r\nHost: " + host + "\r\nUser-Agent: " + DEFAULT_USER_AGENT + "\r\nConnection: close\r\n\r\n";
+	}
+
+	HTTPFetcherEvent *event = new HTTPFetcherEvent();
+
+	if (send(s, request.c_str(), strlen(request.c_str()), 0) < 0) {
+#if PLATFORM == PLATFORM_WINDOWS
+        Logger::log("HTTP Fetcher: Send failed: %d\n", WSAGetLastError());
+		event->errorCode = WSAGetLastError();
+#elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
+        Logger::log("HTTP Fetcher: Send failed: %s\n",strerror(errno));
+		event->errorCode = strerror(errno);
+#endif
+		createSocket();
+		dispatchEvent(event, HTTPFetcherEvent::EVENT_HTTP_ERROR);
+		return;
+	}
+
+	char *server_reply = (char*)malloc(1);
+	char *rec = server_reply;
+	unsigned long recv_size = 0, totalRec = 0;
+	do {
+		//Receive a reply from the server
+#if PLATFORM == PLATFORM_WINDOWS
+		if ((recv_size = recv(s, rec, 1, 0)) == SOCKET_ERROR) {
+			Logger::log("HTTP Fetcher: recv failed: %d\n", WSAGetLastError());
+			event->errorCode = WSAGetLastError();
+#elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
+		if ((recv_size = recv(s, rec, DEFAULT_PAGE_BUF_SIZE, 0)) == -1) {
+			Logger::log("HTTP Fetcher: recv failed: %s\n", strerror(errno));
+			event->errorCode = strerror(errno);
+#endif
+			dispatchEvent(event, HTTPFetcherEvent::EVENT_HTTP_ERROR);
+			killThread();
+			return;
+		}
+
+		
+		totalRec += recv_size;
+		server_reply = (char*)realloc(server_reply, totalRec + 1);
+		rec = server_reply + totalRec;
+	} while (recv_size != 0 && strstr(server_reply, "\r\n\r\n") == NULL);
+
+	server_reply[totalRec] = '\0';
+	event->data = server_reply;
+
+	if (strlen(event->data) == 0){
+		createSocket();
+		return;
+	}
+
+	char *charIndex = strstr(event->data, "HTTP/");
+    if(charIndex == NULL){
+		killThread();
+		return;
+    }
+    int i;
+	if (sscanf(charIndex + strlen("HTTP/1.1"), "%d", &i) != 1 || i < 200 || i>299) {
+		event->errorCode = i;
+		dispatchEvent(event, HTTPFetcherEvent::EVENT_HTTP_ERROR);
+		killThread();
+		return;
+	}
+	charIndex = strstr(event->data, "Content-Length:");
+	if (charIndex == NULL)
+		charIndex = strstr(event->data, "Content-length:");
+	if (sscanf(charIndex + strlen("content-length: "), "%d", &i) != 1) {
+		dispatchEvent(event, HTTPFetcherEvent::EVENT_HTTP_ERROR);
+		killThread();
+		return;
+	}
+
+	FILE* tempFile;
+	if (storeInFile){
+		if (savePath == "")
+			savePath = path;
+		tempFile = fopen(savePath.c_str(), "wb");
+	}
+
+	free(server_reply);
+	server_reply = (char*)malloc(DEFAULT_PAGE_BUF_SIZE);
+	rec = server_reply;
+	recv_size = 0, totalRec = 0;
+
+	do {
+		//Receive a reply from the server
+#if PLATFORM == PLATFORM_WINDOWS
+		if ((recv_size = recv(s, rec, DEFAULT_PAGE_BUF_SIZE, 0)) == SOCKET_ERROR) {
+			Logger::log("HTTP Fetcher: recv failed: %d\n", WSAGetLastError());
+			event->errorCode = WSAGetLastError();
+#elif PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
+		if ((recv_size = recv(s, rec, DEFAULT_PAGE_BUF_SIZE, 0)) == -1) {
+			Logger::log("HTTP Fetcher: recv failed: %s\n", strerror(errno));
+			event->errorCode = strerror(errno);
+#endif
+			dispatchEvent(event, HTTPFetcherEvent::EVENT_HTTP_ERROR);
+			killThread();
+			return;
+		}
+
+
+		totalRec += recv_size;
+		if (!storeInFile){
+			server_reply = (char*)realloc(server_reply, totalRec + DEFAULT_PAGE_BUF_SIZE);
+			rec = server_reply + totalRec;
+		} else {
+			server_reply[recv_size] = '\0';
+			fwrite(server_reply, 1, recv_size, tempFile);
+		}
+	} while (recv_size !=0 && totalRec < i);
+
+	if (totalRec > i){
+		event->errorCode = HTTPFetcher::HTTPFETCHER_ERROR_WRONG_SIZE;
+		dispatchEvent(event, HTTPFetcherEvent::EVENT_HTTP_ERROR);
+		killThread();
+		return;
+	}
+	if (storeInFile){
+		event->storedInFile = true;
+		event->data = (char*)malloc(sizeof(char)*(savePath.length() + 1));
+		strcpy(event->data, savePath.c_str());
+		fclose(tempFile);
+	} else {
+		event->data = server_reply;
+	}
+
+	event->contentSize = totalRec;
+	bodyReturn = event->data;
+    dispatchEvent(event, HTTPFetcherEvent::EVENT_HTTP_DATA_RECEIVED);
+	killThread();
+}
+
+void HTTPFetcher::fetchFile(String pathToFile, bool saveToPath, String savePath){
+	path = pathToFile;
+	this->savePath = savePath;
+	this->storeInFile = saveToPath;
+	threadRunning = true;
+	CoreServices::getInstance()->getCore()->createThread(this);
+}
+
+String HTTPFetcher::getData(){
+	return this->bodyReturn;
+}

+ 1 - 1
Core/Contents/Source/PolyWinCore.cpp

@@ -57,7 +57,7 @@ PFNWGLCHOOSEPIXELFORMATARBPROC	wglChoosePixelFormatARB = NULL;
 using namespace Polycode;
 
 long getThreadID() {
-	return 0;
+	return GetCurrentThreadId();
 }
 
 extern Win32Core *core;