#ifndef ECERE_NONET
+#include <stdio.h>
+
+import "List"
import "network"
+#ifndef ECERE_NOSSL
+import "SSLSocket"
+#endif
+
+class ConnectionsHolder
+{
+ List<HTTPConnection> connections { };
+
+ ~ConnectionsHolder()
+ {
+ HTTPConnection c;
+ while((c = connections[0]))
+ delete c; // The HTTPConnection destructor will take out from the list
+ }
+}
-static OldList connections { };
+static ConnectionsHolder holder { };
namespace net;
-#define BUFFERSIZE 65536
+/*static */define HTTPFILE_BUFFERSIZE = 65536;
static Mutex connectionsMutex { };
{
char * name = (char *)key;
delete name;
- }
+ }
}
static class ServerNameCache
return string + c;
}
+#ifdef ECERE_NOSSL
private class HTTPConnection : Socket
+#else
+private class HTTPConnection : SSLSocket
+#endif
{
- HTTPConnection prevConnection, nextConnection;
+ class_no_expansion;
char * server;
int port;
HTTPFile file;
+ bool secure;
processAlone = true;
~HTTPConnection()
{
- if(prevConnection || nextConnection || connections.first == this)
- printf("");
- }
-
- HTTPConnection()
- {
- if(!connections.offset)
- connections.offset = (uint)&((HTTPConnection)0).prevConnection;
+
+ // printf("Before TakeOut we have %d connections:\n", holder.connections.count); for(c : holder.connections) ::PrintLn(c.server); ::PrintLn("");
+ holder.connections.TakeOut(this);
+ /*
+ PrintLn(server, " Connection Closed (", holder.connections.count, ") opened");
+ printf("Now we have %d connections:\n", holder.connections.count);
+ for(c : holder.connections)
+ {
+ ::PrintLn(c.server);
+ }
+ ::PrintLn("");
+
+
+
+ */
+ delete server;
}
void OnDisconnect(int code)
{
connectionsMutex.Wait();
if(file)
- file.connection = null;
- delete server;
- connections.Remove(this);
+ delete file.connection; // This decrements the file's reference
+
connectionsMutex.Release();
+
+ // PrintLn(server, " Disconnected Us");
+
+ delete this; // The 'connections' reference
}
uint Open_OnReceive(const byte * buffer, uint count)
while(!file.done)
{
bool gotEndLine = false;
- for(c = 0; c<count-1; c++)
+ for(c = 0; c<(int)count-1; c++)
{
if(buffer[c] == '\r' && buffer[c+1] == '\n')
{
gotEndLine = true;
break;
}
- }
+ }
if(!gotEndLine)
// Incomplete packet
return pos;
Log(line);
*/
- /*
+#ifdef _DEBUG
fwrite(buffer, 1, c, stdout);
puts("");
- */
+#endif
if(!c)
{
{
//file.openStarted = true;
if((string = GetString((char *)buffer, "HTTP/1.1 ", count)) ||
- (string = GetString((char *)buffer, "HTTP/1.0 ", count)))
+ (string = GetString((char *)buffer, "HTTP/1.0 ", count)))
{
file.status = atoi(string);
}
file.totalSize = atoi(string);
file.totalSizeSet = true;
}
+ else if(string = GetString((char *)buffer, "Content-Type: ", count))
+ {
+ char * cr = strstr(string, "\r");
+ char * lf = strstr(string, "\n");
+ int len;
+ if(cr)
+ len = cr - string;
+ else if(lf)
+ len = lf - string;
+ else
+ len = strlen(string);
+
+ file.contentType = new char[len+1];
+ memcpy(file.contentType, string, len);
+ file.contentType[len] = 0;
+ }
+ else if(string = GetString((char *)buffer, "Content-disposition: ", count))
+ {
+ char * cr = strstr(string, "\r");
+ char * lf = strstr(string, "\n");
+ int len;
+ if(cr)
+ len = cr - string;
+ else if(lf)
+ len = lf - string;
+ else
+ len = strlen(string);
+
+ file.contentDisposition = new char[len+1];
+ memcpy(file.contentDisposition, string, len);
+ file.contentDisposition[len] = 0;
+ }
else if(string = GetString((char *)buffer, "Connection: ", count))
{
if(!strnicmp(string, "close", strlen("close")))
{
//connection.file = null;
file.connection.file = null;
- file.connection = null;
+ delete file.connection; // This decrements the file's reference
}
}
return pos;
}
- read = Min(count, BUFFERSIZE - file.bufferCount);
+ read = Min(count, HTTPFILE_BUFFERSIZE - file.bufferCount);
if(file.chunked)
{
read = Min(read, file.chunkSize);
return read;
}
else
- return count;
+ return count;
}
}
public class HTTPFile : File
{
+ bool reuseConnection;
+ reuseConnection = true;
public:
+ property bool reuseConnection
+ {
+ set { reuseConnection = value; }
+ get { return reuseConnection; }
+ }
+ property String contentType
+ {
+ get { return contentType; }
+ }
+ property String contentDisposition
+ {
+ get { return contentDisposition; }
+ }
+
bool OpenURL(char * name, char * referer, char * relocation)
{
+ return RetrieveHead(name, referer, relocation, false);
+ }
+
+private:
+
+ bool RetrieveHead(char * name, char * referer, char * relocation, bool askBody)
+ {
bool result = false;
- if(this && strstr(name, "http://") == name)
+ String http, https;
+ if(!this || !name) return false;
+ http = strstr(name, "http://");
+ if(http != name) http = null;
+ https = http ? null : strstr(name, "https://"); if(https && https != name) https = null;
+
+ askedBody = askBody;
+
+ done = false;
+ delete contentType;
+ delete contentDisposition;
+ // ::PrintLn("Opening ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
+ if(this && (http || https))
{
char server[1024];
char msg[1024];
int len;
- char * serverStart = ecere::net::GetString(name, "http://", 0);
+ char * serverStart = http ? ecere::net::GetString(name, "http://", 0) : ecere::net::GetString(name, "https://", 0);
char * fileName = strstr(serverStart, "/");
- int port = 80;
+ int port = https ? 443 : 80;
char * colon;
bool reuse = false;
HTTPConnection connection = null;
if(relocation && !fileName && name[strlen(name)-1] != '/')
{
- strcpy(relocation, "http://");
+ strcpy(relocation, http ? "http://" : "https://");
strcat(relocation, server);
strcat(relocation, "/");
}
{
this.connection.file = null;
if(close)
- {
this.connection.Disconnect(0);
- delete this.connection;
- }
- this.connection = null;
+ delete this.connection;
}
if(chunked)
{
- connectionsMutex.Release();
- while(this.connection && this.connection.file)
+ while(this.connection && this.connection.connected && this.connection.file)
{
+ connectionsMutex.Release();
this.connection.Process();
+ connectionsMutex.Wait();
}
- connectionsMutex.Wait();
- }
-
- for(connection = connections.first; connection; connection = connection.nextConnection)
- {
- if(!strcmpi(connection.server, server) && connection.port == port && !connection.file)
- break;
}
- if(connection)
+ if(reuseConnection)
{
- incref connection;
- reuse = true;
-
- connection.file = this;
+ bool retry = true;
+ while(retry)
+ {
+ retry = false;
+ connection = null;
+ for(c : holder.connections)
+ {
+ if(!strcmpi(c.server, server) && c.port == port && c.secure == (https ? true : false))
+ {
+ if(!c.file && c.connected)
+ {
+ connection = c; // TOFIX: 'incref c' doesn't work
+ incref connection; // HTTPFile reference if we keep it
+ connectionsMutex.Release();
+ connection.ProcessTimeOut(0.000001);
+ connectionsMutex.Wait();
+ if(!connection.connected || connection.file)
+ {
+ // We're disconnected or reused already...
+ retry = true;
+ delete connection;
+ }
+ break;
+ }
+ }
+ }
+ }
+ if(connection)
+ {
+ // ::PrintLn("Reusing Connection ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
+ reuse = true;
+ connection.file = this;
+ }
}
+ tryagain:
if(!connection)
{
char ipAddress[1024];
- connection = HTTPConnection { };
- //incref connection;
+ connection = HTTPConnection
+ {
+#ifndef ECERE_NOSSL
+ autoEstablish = https ? true : false
+#endif
+ };
+ incref connection; // HTTPFile reference on success
connection.file = this;
connectionsMutex.Release();
- if(serverNameCache.Resolve(server, ipAddress) && connection.Connect(ipAddress /*server*/, port))
+ if(serverNameCache.Resolve(server, ipAddress))
{
- connectionsMutex.Wait();
+ // ::PrintLn("No Connection - Connecting ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
+ if(connection.Connect(ipAddress /*server*/, port))
+ {
+ //::PrintLn("Successfully Connected for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
+ //::PrintLn("Waiting on connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
+ connectionsMutex.Wait();
- connections.Add(connection);
- connection.server = CopyString(server);
- connection.port = port;
+ connection.server = CopyString(server);
+ connection.port = port;
+ connection.secure = https ? true : false;
+
+ //::PrintLn("Got connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
+ holder.connections.Add(connection);
+ /*
+ printf("Now we have %d connections:\n", holder.connections.count);
+ for(c : holder.connections)
+ {
+ String s = c.server;
+ ::Print("Server: ");
+ ::PrintLn(c.server);
+ }
+ ::PrintLn("");
+ */
+ incref connection; // Global List Reference
+ }
+ else
+ {
+ // ::PrintLn("Connection Failed for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
+ connectionsMutex.Wait();
+ delete connection;
+ }
}
else
{
if(connection)
{
+ incref connection; // local reference
+
connection.OnReceive = HTTPConnection::Open_OnReceive;
connection.file = this;
this.connection = connection;
this.relocation = relocation;
//openStarted = false;
- strcpy(msg, "GET /");
+ totalSizeSet = false; // HEAD will sometimes give you 0!
+ strcpy(msg, askBody ? "GET /" : "HEAD /");
if(fileName)
{
//strcat(msg, " HTTP/1.0\r\nHost: ");
strcat(msg, server);
strcat(msg, "\r\n");
- strcat(msg, "Accept-Charset: ISO-8859-1\r\n");
- //strcat(msg, "Accept-Charset: UTF-8\r\n");
+ strcat(msg, "Accept-Charset: UTF-8\r\n");
+ //strcat(msg, "Accept-Charset: ISO-8859-1\r\n");
strcat(msg, "Connection: Keep-Alive\r\n");
if(referer)
{
strcat(msg, referer);
strcat(msg, "\r\n");
}
+ /*else
+ {
+ strcat(msg, "Referer: ");
+ strcat(msg, server);
+ strcat(msg, "\r\n");
+ }*/
strcat(msg, "\r\n");
len = strlen(msg);
+ //::PrintLn("Releasing connectionsMutex before GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
connectionsMutex.Release();
+ // ::PrintLn("Sending GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
connection.Send(msg, len);
- while(this.connection && !done)
+ while(this.connection && this.connection.connected && !done)
{
this.connection.Process();
}
+ //::PrintLn("Got DONE for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
if(this.connection)
{
+ if(name != location)
+ {
+ delete location;
+ location = CopyString(name);
+ }
if(status == 200 || (!status && totalSizeSet))
{
- this.connection.OnReceive = HTTPConnection::Read_OnReceive;
+ if(askBody)
+ this.connection.OnReceive = HTTPConnection::Read_OnReceive;
+
result = true;
connectionsMutex.Wait();
}
else
{
- if(chunked)
+ if(askBody)
{
- bool wait = false;
- this.connection.OnReceive = HTTPConnection::Read_OnReceive;
- while(!eof)
+ if(chunked)
{
- if(!this.connection)
- eof = true;
- else
+ bool wait = false;
+ this.connection.OnReceive = HTTPConnection::Read_OnReceive;
+ while(!eof)
{
- // First time check if we already have bytes, second time wait for an event
- this.connection.Process();
- wait = true;
- }
+ if(!this.connection)
+ eof = true;
+ else
+ {
+ // First time check if we already have bytes, second time wait for an event
+ this.connection.Process();
+ wait = true;
+ }
+ }
}
- }
- else if(totalSizeSet)
- {
- done = false;
- this.connection.OnReceive = HTTPConnection::Read_OnReceive;
- while(this.connection && position < totalSize)
+ else if(totalSizeSet)
{
- connection.Process();
- position += bufferCount;
- bufferCount = 0;
+ done = false;
+ this.connection.OnReceive = HTTPConnection::Read_OnReceive;
+ while(this.connection && this.connection.connected && position < totalSize)
+ {
+ connection.Process();
+ position += bufferCount;
+ bufferCount = 0;
+ }
}
}
if(close)
{
this.connection.Disconnect(0);
- delete this.connection;
connection = null;
}
}
-
status = 0;
- this.connection = null;
+ delete this.connection; // This decrements the file's reference
this.relocation = null;
totalSize = 0;
totalSizeSet = false;
{
connectionsMutex.Wait();
}
- if(reuse)
+ if(reuse && !status && connection && !connection.connected)
+ {
+ delete connection;
+ reuse = false;
+ goto tryagain;
+ }
+ else
delete connection;
}
connectionsMutex.Release();
return result;
}
-private:
-
~HTTPFile()
{
+ delete location;
+ delete contentType;
+ delete contentDisposition;
{
connectionsMutex.Wait();
if(connection)
{
- if(totalSizeSet)
+ if(totalSizeSet && askedBody)
{
done = false;
this.connection.OnReceive = HTTPConnection::Read_OnReceive;
- while(this.connection && position < totalSize)
+ while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize)
{
+ connectionsMutex.Release();
connection.Process();
+ connectionsMutex.Wait();
position += bufferCount;
bufferCount = 0;
}
{
connection.file = null;
if(close)
- {
connection.Disconnect(0);
- delete connection;
- }
- connection = null;
+ delete connection;
}
}
connectionsMutex.Release();
if(chunked)
{
- while(connection && connection.file)
+ while(connection && connection.connected && connection.file)
{
+ connectionsMutex.Release();
connection.Process();
+ connectionsMutex.Wait();
}
}
+ //::PrintLn("Done with ", (uint64)this);
}
}
uint readSize = size * count;
uint read = 0;
bool wait = false;
+ Time lastTime = GetTime();
+
+ if(!askedBody)
+ {
+ askedBody = true;
+ if(!RetrieveHead(this.location, null, null, true))
+ return 0;
+ }
if(totalSizeSet && position >= totalSize)
eof = true;
if(numbytes)
{
+ lastTime = GetTime();
memcpy(buffer + read, this.buffer + bufferPos, numbytes);
bufferPos += numbytes;
position += numbytes;
read += numbytes;
- if(bufferPos > BUFFERSIZE / 2)
+ lastTime = GetTime();
+
+ if(bufferPos > HTTPFILE_BUFFERSIZE / 2)
{
// Shift bytes back to beginning of buffer
uint shift = bufferCount - bufferPos;
}
else
{
- if(!connection)
+ if(!connection || !connection.connected)
eof = true;
else
{
// First time check if we already have bytes, second time wait for an event
connection.Process();
+ if(wait && bufferCount - bufferPos == 0 && GetTime() - lastTime > 5)
+ eof = true;
wait = true;
- }
+ }
}
if(totalSizeSet && position >= totalSize)
eof = true;
bool Seek(int pos, FileSeekMode mode)
{
+ if(mode == start && bufferPos == 0 && pos <= bufferCount & pos >= 0)
+ {
+ bufferPos = pos;
+ return true;
+ }
+ else if(mode == current && bufferPos == 0 && (position + pos) <= bufferCount & (position + pos) >= 0)
+ {
+ bufferPos = position + pos;
+ return true;
+ }
+ else if(mode == end && totalSizeSet && bufferPos == 0 && bufferCount == totalSize && (totalSize - pos) <= bufferCount & (totalSize - pos) >= 0)
+ {
+ bufferPos = totalSize - pos;
+ return true;
+ }
return false;
}
{
aborted = true;
}
+private:
+
+ bool askedBody;
HTTPConnection connection;
uint position;
bool close;
uint chunkSize;
char * relocation;
+ String location;
// Buffering...
- byte buffer[BUFFERSIZE];
+ byte buffer[HTTPFILE_BUFFERSIZE];
uint bufferPos;
uint bufferCount;
bool aborted;
bool totalSizeSet;
+
+ String contentType;
+ String contentDisposition;
}
public HTTPFile FileOpenURL(char * name)
{
delete f;
return null;
- }
+ }
}
#endif