11 class ConnectionsHolder
13 List<HTTPConnection> connections { };
18 while((c = connections[0]))
19 delete c; // The HTTPConnection destructor will take out from the list
23 static ConnectionsHolder holder { };
27 /*static */define HTTPFILE_BUFFERSIZE = 65536;
29 static Mutex connectionsMutex { };
31 static class ServerNode : BTNode
41 static class ServerNameCache
45 CompareKey = (void *)BinaryTree::CompareString;
52 while((server = (ServerNode)servers.root))
54 servers.Remove(server);
58 bool Resolve(const char * host, char * address)
62 server = (ServerNode)servers.FindString(host);
65 server = ServerNode { key = (uintptr)CopyString(host) };
67 server.resolved = GetAddressFromName(host, server.address);
71 strcpy(address, server.address);
72 return server.resolved;
76 static ServerNameCache serverNameCache { };
78 static const char * GetString(const char * string, const char * what, int count)
81 for(c = 0; what[c]; c++)
83 if((count && c >= count) || (string[c] != what[c] && tolower(string[c]) != tolower(what[c])))
90 private class HTTPConnection : Socket
92 private class HTTPConnection : SSLSocket
105 // printf("Before TakeOut we have %d connections:\n", holder.connections.count); for(c : holder.connections) ::PrintLn(c.server); ::PrintLn("");
106 holder.connections.TakeOut(this);
108 PrintLn(server, " Connection Closed (", holder.connections.count, ") opened");
109 printf("Now we have %d connections:\n", holder.connections.count);
110 for(c : holder.connections)
119 void OnDisconnect(int code)
121 connectionsMutex.Wait();
123 delete file.connection; // This decrements the file's reference
125 connectionsMutex.Release();
127 // PrintLn(server, " Disconnected Us");
129 delete this; // The 'connections' reference
132 uint Open_OnReceive(const byte * buffer, uint count)
134 HTTPFile file = this.file;
139 bool gotEndLine = false;
140 for(c = 0; c<(int)count-1; c++)
142 if(buffer[c] == '\r' && buffer[c+1] == '\n')
153 const char * string = (const char *)buffer;
156 fwrite(buffer, 1, c, stdout);
162 //if(file.openStarted)
167 //file.openStarted = true;
168 if((string = GetString((const char *)buffer, "HTTP/1.1 ", count)) ||
169 (string = GetString((const char *)buffer, "HTTP/1.0 ", count)))
171 file.status = atoi(string);
173 else if((string = GetString((const char *)buffer, "Transfer-Encoding: ", count)))
175 if(!strnicmp(string, "chunked", strlen("chunked")))
180 else if((string = GetString((const char *)buffer, "Content-Length: ", count)))
182 file.totalSize = atoi(string);
183 file.totalSizeSet = true;
185 else if((string = GetString((const char *)buffer, "Content-Type: ", count)))
187 char * cr = strstr(string, "\r");
188 char * lf = strstr(string, "\n");
195 len = strlen(string);
197 file.contentType = new char[len+1];
198 memcpy(file.contentType, string, len);
199 file.contentType[len] = 0;
201 else if((string = GetString((const char *)buffer, "Content-disposition: ", count)))
203 char * cr = strstr(string, "\r");
204 char * lf = strstr(string, "\n");
211 len = strlen(string);
213 file.contentDisposition = new char[len+1];
214 memcpy(file.contentDisposition, string, len);
215 file.contentDisposition[len] = 0;
217 else if((string = GetString((const char *)buffer, "Connection: ", count)))
219 if(!strnicmp(string, "close", strlen("close")))
224 else if((string = GetString((const char *)buffer, "Location: ", count)))
228 strncpy(file.relocation, (const char *)buffer + 10, c - 10);
229 file.relocation[c - 10] = '\0';
234 if(buffer[c] == '\r') c++;
235 if(buffer[c] == '\n') c++;
248 uint Read_OnReceive(const byte * buffer, uint count)
250 HTTPFile file = this.file;
254 if(file.chunked && !file.chunkSize)
257 const char * string = null;
259 while(pos + c < (int)count-3)
261 if(buffer[pos+c] == '\r' && buffer[pos+c+1] == '\n')
267 if(buffer[pos] == '\r' && buffer[pos+1] =='\n') pos+= 2;
279 string = (const char *)buffer + pos;
285 file.chunkSize = strtol(string, null, 16);
288 file.connection.file = null;
290 file.connection.Disconnect(0);
291 delete file.connection; // This decrements the file's reference
297 read = Min(count, HTTPFILE_BUFFERSIZE - file.bufferCount);
300 read = Min(read, file.chunkSize);
301 file.chunkSize -= read;
305 memcpy(file.buffer + file.bufferCount, buffer, read);
306 file.bufferCount += read;
315 public class HTTPFile : File
317 bool reuseConnection;
318 reuseConnection = true;
320 property bool reuseConnection
322 set { reuseConnection = value; }
323 get { return reuseConnection; }
325 property String contentType
327 get { return contentType; }
329 property String contentDisposition
331 get { return contentDisposition; }
334 bool OpenURL(const char * name, const char * referer, char * relocation)
336 return RetrieveHead(name, referer, relocation, false);
341 bool RetrieveHead(const char * name, const char * referer, char * relocation, bool askBody)
344 const String http, https;
345 if(!this || !name) return false;
346 http = strstr(name, "http://");
347 if(http != name) http = null;
348 https = http ? null : strstr(name, "https://"); if(https && https != name) https = null;
354 delete contentDisposition;
355 // ::PrintLn("Opening ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
356 if(this && (http || https))
361 const char * serverStart = http ? ecere::net::GetString(name, "http://", 0) : ecere::net::GetString(name, "https://", 0);
362 char * fileName = strstr(serverStart, "/");
363 int port = https ? 443 : 80;
366 HTTPConnection connection = null;
372 memcpy(server, serverStart, fileName - serverStart - 1);
373 server[fileName - serverStart - 1] = '\0';
376 strcpy(server, serverStart);
378 if(relocation && !fileName && name[strlen(name)-1] != '/')
380 strcpy(relocation, http ? "http://" : "https://");
381 strcat(relocation, server);
382 strcat(relocation, "/");
385 colon = strchr(server, ':');
388 port = atoi(colon+1);
392 connectionsMutex.Wait();
395 this.connection.file = null;
397 this.connection.Disconnect(0);
398 delete this.connection;
403 while(this.connection && this.connection.connected && this.connection.file)
405 connectionsMutex.Release();
406 this.connection.Process();
407 connectionsMutex.Wait();
418 for(c : holder.connections)
420 if(!strcmpi(c.server, server) && c.port == port && c.secure == (https ? true : false))
422 if(!c.file && c.connected)
424 connection = c; // TOFIX: 'incref c' doesn't work
425 incref connection; // HTTPFile reference if we keep it
426 connectionsMutex.Release();
427 connection.ProcessTimeOut(0.000001);
428 connectionsMutex.Wait();
429 if(!connection.connected || connection.file)
431 // We're disconnected or reused already...
442 // ::PrintLn("Reusing Connection ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
444 connection.file = this;
451 char ipAddress[1024];
452 connection = HTTPConnection
455 autoEstablish = https ? true : false
458 incref connection; // HTTPFile reference on success
460 connection.file = this;
462 connectionsMutex.Release();
464 if(serverNameCache.Resolve(server, ipAddress))
466 // ::PrintLn("No Connection - Connecting ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
467 if(connection.Connect(ipAddress /*server*/, port))
469 //::PrintLn("Successfully Connected for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
470 //::PrintLn("Waiting on connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
471 connectionsMutex.Wait();
473 connection.server = CopyString(server);
474 connection.port = port;
475 connection.secure = https ? true : false;
477 //::PrintLn("Got connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
478 holder.connections.Add(connection);
480 printf("Now we have %d connections:\n", holder.connections.count);
481 for(c : holder.connections)
489 incref connection; // Global List Reference
493 // ::PrintLn("Connection Failed for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
494 connectionsMutex.Wait();
500 connectionsMutex.Wait();
507 incref connection; // local reference
509 connection.OnReceive = HTTPConnection::Open_OnReceive;
510 connection.file = this;
511 this.connection = connection;
512 this.relocation = relocation;
513 //openStarted = false;
515 totalSizeSet = false; // HEAD will sometimes give you 0!
516 strcpy(msg, askBody ? "GET /" : "HEAD /");
523 for(c = 0; (ch = fileName[c]); c++)
525 if(ch <= 32 || ch > 128)
529 nibble = (ch & 0xF0) >> 4;
530 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
532 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
540 strcat(msg, " HTTP/1.1\r\nHost: ");
541 //strcat(msg, " HTTP/1.0\r\nHost: ");
544 strcat(msg, "Accept-Charset: UTF-8\r\n");
545 //strcat(msg, "Accept-Charset: ISO-8859-1\r\n");
546 strcat(msg, "Connection: Keep-Alive\r\n");
549 strcat(msg, "Referer: ");
550 strcat(msg, referer);
556 //::PrintLn("Releasing connectionsMutex before GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
557 connectionsMutex.Release();
559 // ::PrintLn("Sending GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
560 connection.Send(msg, len);
562 while(this.connection && this.connection.connected && !done)
564 this.connection.Process();
566 //::PrintLn("Got DONE for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
573 location = CopyString(name);
575 if(status == 200 || (!status && totalSizeSet))
578 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
581 connectionsMutex.Wait();
590 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
597 // First time check if we already have bytes, second time wait for an event
598 this.connection.Process();
603 else if(totalSizeSet)
605 // Is it possible to have Content-Length set but not chunked?
607 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
608 while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize)
610 connection.Process();
611 position += bufferCount - bufferPos;
618 connectionsMutex.Wait();
621 this.connection.OnReceive = null;
622 this.connection.file = null;
626 this.connection.Disconnect(0);
632 delete this.connection; // This decrements the file's reference
633 this.relocation = null;
635 totalSizeSet = false;
646 connectionsMutex.Wait();
648 if(reuse && !status && connection && !connection.connected)
657 connectionsMutex.Release();
666 delete contentDisposition;
668 connectionsMutex.Wait();
671 if(totalSizeSet && askedBody)
674 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
675 while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize)
677 connectionsMutex.Release();
678 connection.Process();
679 connectionsMutex.Wait();
680 position += bufferCount - bufferPos;
689 connection.file = null;
691 connection.Disconnect(0);
695 connectionsMutex.Release();
699 while(connection && connection.connected && connection.file)
701 connectionsMutex.Release();
702 connection.Process();
703 connectionsMutex.Wait();
706 //::PrintLn("Done with ", (uint64)this);
710 int Read(byte * buffer, uint size, uint count)
712 uint readSize = size * count;
715 Time lastTime = GetTime();
720 if(!RetrieveHead(this.location, null, null, true))
724 if(totalSizeSet && position >= totalSize)
726 while(!eof && read < readSize && !aborted)
728 uint numbytes = bufferCount - bufferPos;
729 numbytes = Min(numbytes, readSize - read);
731 numbytes = Min(numbytes, totalSize - position);
735 lastTime = GetTime();
736 memcpy(buffer + read, this.buffer + bufferPos, numbytes);
737 bufferPos += numbytes;
738 position += numbytes;
741 lastTime = GetTime();
743 if(bufferPos > HTTPFILE_BUFFERSIZE / 2)
745 // Shift bytes back to beginning of buffer
746 uint shift = bufferCount - bufferPos;
748 memmove(this.buffer, this.buffer + bufferPos, shift);
749 bufferCount -= bufferPos;
755 if(!connection || !connection.connected)
759 // First time check if we already have bytes, second time wait for an event
760 connection.Process();
761 if(wait && bufferCount - bufferPos == 0 && GetTime() - lastTime > 5)
766 if(totalSizeSet && position >= totalSize)
772 int Write(const byte * buffer, uint size, uint count)
779 int read = Read(ch, 1, 1);
780 return !eof && read != 0;
788 bool Puts(const char * string)
793 bool Seek(int pos, FileSeekMode mode)
795 if(mode == start && bufferPos == 0 && pos <= bufferCount && pos >= 0)
800 else if(mode == current && bufferPos == 0 && ((int)position + pos) <= bufferCount && ((int)position + pos) >= 0)
802 bufferPos = position + pos;
805 else if(mode == end && totalSizeSet && bufferPos == 0 && bufferCount == totalSize && ((int)totalSize - pos) <= bufferCount && ((int)totalSize - pos) >= 0)
807 bufferPos = totalSize - pos;
836 HTTPConnection connection;
849 byte buffer[HTTPFILE_BUFFERSIZE];
856 String contentDisposition;
859 public HTTPFile FileOpenURL(const char * name)
862 if(f.OpenURL(name, null, null))