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
37 char * name = (char *)key;
42 static class ServerNameCache
46 CompareKey = (void *)BinaryTree::CompareString;
53 while((server = (ServerNode)servers.root))
55 servers.Remove(server);
59 bool Resolve(char * host, char * address)
63 server = (ServerNode)servers.FindString(host);
66 server = ServerNode { key = (uintptr)CopyString(host) };
68 server.resolved = GetAddressFromName(host, server.address);
72 strcpy(address, server.address);
73 return server.resolved;
77 static ServerNameCache serverNameCache { };
79 static char * GetString(char * string, char * what, int count)
82 for(c = 0; what[c]; c++)
84 if((count && c >= count) || (string[c] != what[c] && tolower(string[c]) != tolower(what[c])))
91 private class HTTPConnection : Socket
93 private class HTTPConnection : SSLSocket
106 // printf("Before TakeOut we have %d connections:\n", holder.connections.count); for(c : holder.connections) ::PrintLn(c.server); ::PrintLn("");
107 holder.connections.TakeOut(this);
109 PrintLn(server, " Connection Closed (", holder.connections.count, ") opened");
110 printf("Now we have %d connections:\n", holder.connections.count);
111 for(c : holder.connections)
120 void OnDisconnect(int code)
122 connectionsMutex.Wait();
124 delete file.connection; // This decrements the file's reference
126 connectionsMutex.Release();
128 // PrintLn(server, " Disconnected Us");
130 delete this; // The 'connections' reference
133 uint Open_OnReceive(const byte * buffer, uint count)
135 HTTPFile file = this.file;
140 bool gotEndLine = false;
141 for(c = 0; c<(int)count-1; c++)
143 if(buffer[c] == '\r' && buffer[c+1] == '\n')
154 char * string = (char *)buffer;
157 fwrite(buffer, 1, c, stdout);
163 //if(file.openStarted)
168 //file.openStarted = true;
169 if((string = GetString((char *)buffer, "HTTP/1.1 ", count)) ||
170 (string = GetString((char *)buffer, "HTTP/1.0 ", count)))
172 file.status = atoi(string);
174 else if((string = GetString((char *)buffer, "Transfer-Encoding: ", count)))
176 if(!strnicmp(string, "chunked", strlen("chunked")))
181 else if((string = GetString((char *)buffer, "Content-Length: ", count)))
183 file.totalSize = atoi(string);
184 file.totalSizeSet = true;
186 else if((string = GetString((char *)buffer, "Content-Type: ", count)))
188 char * cr = strstr(string, "\r");
189 char * lf = strstr(string, "\n");
196 len = strlen(string);
198 file.contentType = new char[len+1];
199 memcpy(file.contentType, string, len);
200 file.contentType[len] = 0;
202 else if((string = GetString((char *)buffer, "Content-disposition: ", count)))
204 char * cr = strstr(string, "\r");
205 char * lf = strstr(string, "\n");
212 len = strlen(string);
214 file.contentDisposition = new char[len+1];
215 memcpy(file.contentDisposition, string, len);
216 file.contentDisposition[len] = 0;
218 else if((string = GetString((char *)buffer, "Connection: ", count)))
220 if(!strnicmp(string, "close", strlen("close")))
225 else if((string = GetString((char *)buffer, "Location: ", count)))
229 strncpy(file.relocation, (char *)buffer + 10, c - 10);
230 file.relocation[c - 10] = '\0';
235 if(buffer[c] == '\r') c++;
236 if(buffer[c] == '\n') c++;
249 uint Read_OnReceive(const byte * buffer, uint count)
251 HTTPFile file = this.file;
255 if(file.chunked && !file.chunkSize)
258 char * string = null;
260 while(pos + c < (int)count-3)
262 if(buffer[pos+c] == '\r' && buffer[pos+c+1] == '\n')
268 if(buffer[pos] == '\r' && buffer[pos+1] =='\n') pos+= 2;
280 string = buffer + pos;
286 file.chunkSize = strtol(string, null, 16);
289 file.connection.file = null;
291 file.connection.Disconnect(0);
292 delete file.connection; // This decrements the file's reference
298 read = Min(count, HTTPFILE_BUFFERSIZE - file.bufferCount);
301 read = Min(read, file.chunkSize);
302 file.chunkSize -= read;
306 memcpy(file.buffer + file.bufferCount, buffer, read);
307 file.bufferCount += read;
316 public class HTTPFile : File
318 bool reuseConnection;
319 reuseConnection = true;
321 property bool reuseConnection
323 set { reuseConnection = value; }
324 get { return reuseConnection; }
326 property String contentType
328 get { return contentType; }
330 property String contentDisposition
332 get { return contentDisposition; }
335 bool OpenURL(char * name, char * referer, char * relocation)
337 return RetrieveHead(name, referer, relocation, false);
342 bool RetrieveHead(char * name, char * referer, char * relocation, bool askBody)
346 if(!this || !name) return false;
347 http = strstr(name, "http://");
348 if(http != name) http = null;
349 https = http ? null : strstr(name, "https://"); if(https && https != name) https = null;
355 delete contentDisposition;
356 // ::PrintLn("Opening ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
357 if(this && (http || https))
362 char * serverStart = http ? ecere::net::GetString(name, "http://", 0) : ecere::net::GetString(name, "https://", 0);
363 char * fileName = strstr(serverStart, "/");
364 int port = https ? 443 : 80;
367 HTTPConnection connection = null;
373 memcpy(server, serverStart, fileName - serverStart - 1);
374 server[fileName - serverStart - 1] = '\0';
377 strcpy(server, serverStart);
379 if(relocation && !fileName && name[strlen(name)-1] != '/')
381 strcpy(relocation, http ? "http://" : "https://");
382 strcat(relocation, server);
383 strcat(relocation, "/");
386 colon = strchr(server, ':');
389 port = atoi(colon+1);
393 connectionsMutex.Wait();
396 this.connection.file = null;
398 this.connection.Disconnect(0);
399 delete this.connection;
404 while(this.connection && this.connection.connected && this.connection.file)
406 connectionsMutex.Release();
407 this.connection.Process();
408 connectionsMutex.Wait();
419 for(c : holder.connections)
421 if(!strcmpi(c.server, server) && c.port == port && c.secure == (https ? true : false))
423 if(!c.file && c.connected)
425 connection = c; // TOFIX: 'incref c' doesn't work
426 incref connection; // HTTPFile reference if we keep it
427 connectionsMutex.Release();
428 connection.ProcessTimeOut(0.000001);
429 connectionsMutex.Wait();
430 if(!connection.connected || connection.file)
432 // We're disconnected or reused already...
443 // ::PrintLn("Reusing Connection ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
445 connection.file = this;
452 char ipAddress[1024];
453 connection = HTTPConnection
456 autoEstablish = https ? true : false
459 incref connection; // HTTPFile reference on success
461 connection.file = this;
463 connectionsMutex.Release();
465 if(serverNameCache.Resolve(server, ipAddress))
467 // ::PrintLn("No Connection - Connecting ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
468 if(connection.Connect(ipAddress /*server*/, port))
470 //::PrintLn("Successfully Connected for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
471 //::PrintLn("Waiting on connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
472 connectionsMutex.Wait();
474 connection.server = CopyString(server);
475 connection.port = port;
476 connection.secure = https ? true : false;
478 //::PrintLn("Got connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
479 holder.connections.Add(connection);
481 printf("Now we have %d connections:\n", holder.connections.count);
482 for(c : holder.connections)
490 incref connection; // Global List Reference
494 // ::PrintLn("Connection Failed for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
495 connectionsMutex.Wait();
501 connectionsMutex.Wait();
508 incref connection; // local reference
510 connection.OnReceive = HTTPConnection::Open_OnReceive;
511 connection.file = this;
512 this.connection = connection;
513 this.relocation = relocation;
514 //openStarted = false;
516 totalSizeSet = false; // HEAD will sometimes give you 0!
517 strcpy(msg, askBody ? "GET /" : "HEAD /");
524 for(c = 0; (ch = fileName[c]); c++)
526 if(ch <= 32 || ch > 128)
530 nibble = (ch & 0xF0) >> 4;
531 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
533 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
541 strcat(msg, " HTTP/1.1\r\nHost: ");
542 //strcat(msg, " HTTP/1.0\r\nHost: ");
545 strcat(msg, "Accept-Charset: UTF-8\r\n");
546 //strcat(msg, "Accept-Charset: ISO-8859-1\r\n");
547 strcat(msg, "Connection: Keep-Alive\r\n");
550 strcat(msg, "Referer: ");
551 strcat(msg, referer);
557 //::PrintLn("Releasing connectionsMutex before GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
558 connectionsMutex.Release();
560 // ::PrintLn("Sending GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
561 connection.Send(msg, len);
563 while(this.connection && this.connection.connected && !done)
565 this.connection.Process();
567 //::PrintLn("Got DONE for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
574 location = CopyString(name);
576 if(status == 200 || (!status && totalSizeSet))
579 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
582 connectionsMutex.Wait();
591 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
598 // First time check if we already have bytes, second time wait for an event
599 this.connection.Process();
604 else if(totalSizeSet)
606 // Is it possible to have Content-Length set but not chunked?
608 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
609 while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize)
611 connection.Process();
612 position += bufferCount - bufferPos;
619 connectionsMutex.Wait();
622 this.connection.OnReceive = null;
623 this.connection.file = null;
627 this.connection.Disconnect(0);
633 delete this.connection; // This decrements the file's reference
634 this.relocation = null;
636 totalSizeSet = false;
647 connectionsMutex.Wait();
649 if(reuse && !status && connection && !connection.connected)
658 connectionsMutex.Release();
667 delete contentDisposition;
669 connectionsMutex.Wait();
672 if(totalSizeSet && askedBody)
675 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
676 while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize)
678 connectionsMutex.Release();
679 connection.Process();
680 connectionsMutex.Wait();
681 position += bufferCount - bufferPos;
690 connection.file = null;
692 connection.Disconnect(0);
696 connectionsMutex.Release();
700 while(connection && connection.connected && connection.file)
702 connectionsMutex.Release();
703 connection.Process();
704 connectionsMutex.Wait();
707 //::PrintLn("Done with ", (uint64)this);
711 int Read(byte * buffer, uint size, uint count)
713 uint readSize = size * count;
716 Time lastTime = GetTime();
721 if(!RetrieveHead(this.location, null, null, true))
725 if(totalSizeSet && position >= totalSize)
727 while(!eof && read < readSize && !aborted)
729 uint numbytes = bufferCount - bufferPos;
730 numbytes = Min(numbytes, readSize - read);
732 numbytes = Min(numbytes, totalSize - position);
736 lastTime = GetTime();
737 memcpy(buffer + read, this.buffer + bufferPos, numbytes);
738 bufferPos += numbytes;
739 position += numbytes;
742 lastTime = GetTime();
744 if(bufferPos > HTTPFILE_BUFFERSIZE / 2)
746 // Shift bytes back to beginning of buffer
747 uint shift = bufferCount - bufferPos;
749 memmove(this.buffer, this.buffer + bufferPos, shift);
750 bufferCount -= bufferPos;
756 if(!connection || !connection.connected)
760 // First time check if we already have bytes, second time wait for an event
761 connection.Process();
762 if(wait && bufferCount - bufferPos == 0 && GetTime() - lastTime > 5)
767 if(totalSizeSet && position >= totalSize)
773 int Write(byte * buffer, uint size, uint count)
780 int read = Read(ch, 1, 1);
781 return !eof && read != 0;
789 bool Puts(char * string)
794 bool Seek(int pos, FileSeekMode mode)
796 if(mode == start && bufferPos == 0 && pos <= bufferCount && pos >= 0)
801 else if(mode == current && bufferPos == 0 && (position + pos) <= bufferCount && (position + pos) >= 0)
803 bufferPos = position + pos;
806 else if(mode == end && totalSizeSet && bufferPos == 0 && bufferCount == totalSize && (totalSize - pos) <= bufferCount && (totalSize - pos) >= 0)
808 bufferPos = totalSize - pos;
837 HTTPConnection connection;
850 byte buffer[HTTPFILE_BUFFERSIZE];
857 String contentDisposition;
860 public HTTPFile FileOpenURL(char * name)
863 if(f.OpenURL(name, null, null))