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
107 // printf("Before TakeOut we have %d connections:\n", holder.connections.count); for(c : holder.connections) ::PrintLn(c.server); ::PrintLn("");
108 holder.connections.TakeOut(this);
110 PrintLn(server, " Connection Closed (", holder.connections.count, ") opened");
111 printf("Now we have %d connections:\n", holder.connections.count);
112 for(c : holder.connections)
124 void OnDisconnect(int code)
126 connectionsMutex.Wait();
128 delete file.connection; // This decrements the file's reference
130 connectionsMutex.Release();
132 // PrintLn(server, " Disconnected Us");
134 delete this; // The 'connections' reference
137 uint Open_OnReceive(const byte * buffer, uint count)
139 HTTPFile file = this.file;
144 bool gotEndLine = false;
145 for(c = 0; c<(int)count-1; c++)
147 if(buffer[c] == '\r' && buffer[c+1] == '\n')
158 char * string = (char *)buffer;
162 memcpy(line, buffer, c+2);
168 fwrite(buffer, 1, c, stdout);
174 //if(file.openStarted)
179 //file.openStarted = true;
180 if((string = GetString((char *)buffer, "HTTP/1.1 ", count)) ||
181 (string = GetString((char *)buffer, "HTTP/1.0 ", count)))
183 file.status = atoi(string);
185 else if(string = GetString((char *)buffer, "Transfer-Encoding: ", count))
187 if(!strnicmp(string, "chunked", strlen("chunked")))
192 else if(string = GetString((char *)buffer, "Content-Length: ", count))
194 file.totalSize = atoi(string);
195 file.totalSizeSet = true;
197 else if(string = GetString((char *)buffer, "Content-Type: ", count))
199 char * cr = strstr(string, "\r");
200 char * lf = strstr(string, "\n");
207 len = strlen(string);
209 file.contentType = new char[len+1];
210 memcpy(file.contentType, string, len);
211 file.contentType[len] = 0;
213 else if(string = GetString((char *)buffer, "Content-disposition: ", count))
215 char * cr = strstr(string, "\r");
216 char * lf = strstr(string, "\n");
223 len = strlen(string);
225 file.contentDisposition = new char[len+1];
226 memcpy(file.contentDisposition, string, len);
227 file.contentDisposition[len] = 0;
229 else if(string = GetString((char *)buffer, "Connection: ", count))
231 if(!strnicmp(string, "close", strlen("close")))
236 else if(string = GetString((char *)buffer, "Location: ", count))
240 strncpy(file.relocation, buffer + 10, c - 10);
241 file.relocation[c - 10] = '\0';
246 if(buffer[c] == '\r') c++;
247 if(buffer[c] == '\n') c++;
260 uint Read_OnReceive(const byte * buffer, uint count)
262 HTTPFile file = this.file;
266 if(file.chunked && !file.chunkSize)
269 char * string = null;
271 while(pos + c < (int)count-3)
273 if(buffer[pos+c] == '\r' && buffer[pos+c+1] == '\n')
279 if(buffer[pos] == '\r' && buffer[pos+1] =='\n') pos+= 2;
291 string = buffer + pos;
297 file.chunkSize = strtol(string, null, 16);
300 //connection.file = null;
301 file.connection.file = null;
302 delete file.connection; // This decrements the file's reference
308 read = Min(count, HTTPFILE_BUFFERSIZE - file.bufferCount);
311 read = Min(read, file.chunkSize);
312 file.chunkSize -= read;
316 memcpy(file.buffer + file.bufferCount, buffer, read);
317 // fwrite(file.buffer, 1, read, stdout);
318 file.bufferCount += read;
320 if(GetCurrentThreadID() == network.mainThreadID)
321 fwrite(buffer, 1, read, stdout);
331 public class HTTPFile : File
333 bool reuseConnection;
334 reuseConnection = true;
336 property bool reuseConnection
338 set { reuseConnection = value; }
339 get { return reuseConnection; }
341 property String contentType
343 get { return contentType; }
345 property String contentDisposition
347 get { return contentDisposition; }
350 bool OpenURL(char * name, char * referer, char * relocation)
352 return RetrieveHead(name, referer, relocation, false);
357 bool RetrieveHead(char * name, char * referer, char * relocation, bool askBody)
361 if(!this || !name) return false;
362 http = strstr(name, "http://");
363 if(http != name) http = null;
364 https = http ? null : strstr(name, "https://"); if(https && https != name) https = null;
370 delete contentDisposition;
371 // ::PrintLn("Opening ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
372 if(this && (http || https))
377 char * serverStart = http ? ecere::net::GetString(name, "http://", 0) : ecere::net::GetString(name, "https://", 0);
378 char * fileName = strstr(serverStart, "/");
379 int port = https ? 443 : 80;
382 HTTPConnection connection = null;
388 memcpy(server, serverStart, fileName - serverStart - 1);
389 server[fileName - serverStart - 1] = '\0';
392 strcpy(server, serverStart);
394 if(relocation && !fileName && name[strlen(name)-1] != '/')
396 strcpy(relocation, http ? "http://" : "https://");
397 strcat(relocation, server);
398 strcat(relocation, "/");
401 colon = strchr(server, ':');
404 port = atoi(colon+1);
408 connectionsMutex.Wait();
411 this.connection.file = null;
413 this.connection.Disconnect(0);
414 delete this.connection;
419 while(this.connection && this.connection.connected && this.connection.file)
421 connectionsMutex.Release();
422 this.connection.Process();
423 connectionsMutex.Wait();
434 for(c : holder.connections)
436 if(!strcmpi(c.server, server) && c.port == port && c.secure == (https ? true : false))
438 if(!c.file && c.connected)
440 connection = c; // TOFIX: 'incref c' doesn't work
441 incref connection; // HTTPFile reference if we keep it
442 connectionsMutex.Release();
443 connection.ProcessTimeOut(0.000001);
444 connectionsMutex.Wait();
445 if(!connection.connected || connection.file)
447 // We're disconnected or reused already...
458 // ::PrintLn("Reusing Connection ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
460 connection.file = this;
467 char ipAddress[1024];
468 connection = HTTPConnection
471 autoEstablish = https ? true : false
474 incref connection; // HTTPFile reference on success
476 connection.file = this;
478 connectionsMutex.Release();
480 if(serverNameCache.Resolve(server, ipAddress))
482 // ::PrintLn("No Connection - Connecting ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
483 if(connection.Connect(ipAddress /*server*/, port))
485 //::PrintLn("Successfully Connected for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
486 //::PrintLn("Waiting on connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
487 connectionsMutex.Wait();
489 connection.server = CopyString(server);
490 connection.port = port;
491 connection.secure = https ? true : false;
493 //::PrintLn("Got connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
494 holder.connections.Add(connection);
496 printf("Now we have %d connections:\n", holder.connections.count);
497 for(c : holder.connections)
505 incref connection; // Global List Reference
509 // ::PrintLn("Connection Failed for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
510 connectionsMutex.Wait();
516 connectionsMutex.Wait();
523 incref connection; // local reference
525 connection.OnReceive = HTTPConnection::Open_OnReceive;
526 connection.file = this;
527 this.connection = connection;
528 this.relocation = relocation;
529 //openStarted = false;
531 totalSizeSet = false; // HEAD will sometimes give you 0!
532 strcpy(msg, askBody ? "GET /" : "HEAD /");
539 for(c = 0; (ch = fileName[c]); c++)
541 if(ch <= 32 || ch > 128)
545 nibble = (ch & 0xF0) >> 4;
546 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
548 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
556 strcat(msg, " HTTP/1.1\r\nHost: ");
557 //strcat(msg, " HTTP/1.0\r\nHost: ");
560 strcat(msg, "Accept-Charset: UTF-8\r\n");
561 //strcat(msg, "Accept-Charset: ISO-8859-1\r\n");
562 strcat(msg, "Connection: Keep-Alive\r\n");
565 strcat(msg, "Referer: ");
566 strcat(msg, referer);
571 strcat(msg, "Referer: ");
578 //::PrintLn("Releasing connectionsMutex before GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
579 connectionsMutex.Release();
581 // ::PrintLn("Sending GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
582 connection.Send(msg, len);
584 while(this.connection && this.connection.connected && !done)
586 this.connection.Process();
588 //::PrintLn("Got DONE for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
595 location = CopyString(name);
597 if(status == 200 || (!status && totalSizeSet))
600 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
603 connectionsMutex.Wait();
612 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
619 // First time check if we already have bytes, second time wait for an event
620 this.connection.Process();
625 else if(totalSizeSet)
628 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
629 while(this.connection && this.connection.connected && position < totalSize)
631 connection.Process();
632 position += bufferCount;
638 connectionsMutex.Wait();
641 this.connection.OnReceive = null;
642 this.connection.file = null;
646 this.connection.Disconnect(0);
652 delete this.connection; // This decrements the file's reference
653 this.relocation = null;
655 totalSizeSet = false;
666 connectionsMutex.Wait();
668 if(reuse && !status && connection && !connection.connected)
677 connectionsMutex.Release();
686 delete contentDisposition;
688 connectionsMutex.Wait();
691 if(totalSizeSet && askedBody)
694 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
695 while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize)
697 connectionsMutex.Release();
698 connection.Process();
699 connectionsMutex.Wait();
700 position += bufferCount;
708 connection.file = null;
710 connection.Disconnect(0);
714 connectionsMutex.Release();
718 while(connection && connection.connected && connection.file)
720 connectionsMutex.Release();
721 connection.Process();
722 connectionsMutex.Wait();
725 //::PrintLn("Done with ", (uint64)this);
729 int Read(byte * buffer, uint size, uint count)
731 uint readSize = size * count;
734 Time lastTime = GetTime();
739 if(!RetrieveHead(this.location, null, null, true))
743 if(totalSizeSet && position >= totalSize)
745 while(!eof && read < readSize && !aborted)
747 uint numbytes = bufferCount - bufferPos;
748 numbytes = Min(numbytes, readSize - read);
750 numbytes = Min(numbytes, totalSize - position);
754 lastTime = GetTime();
755 memcpy(buffer + read, this.buffer + bufferPos, numbytes);
756 bufferPos += numbytes;
757 position += numbytes;
760 lastTime = GetTime();
762 if(bufferPos > HTTPFILE_BUFFERSIZE / 2)
764 // Shift bytes back to beginning of buffer
765 uint shift = bufferCount - bufferPos;
767 memcpy(this.buffer, this.buffer + bufferPos, shift);
768 bufferCount -= bufferPos;
774 if(!connection || !connection.connected)
778 // First time check if we already have bytes, second time wait for an event
779 connection.Process();
780 if(wait && bufferCount - bufferPos == 0 && GetTime() - lastTime > 5)
785 if(totalSizeSet && position >= totalSize)
791 int Write(byte * buffer, uint size, uint count)
798 int read = Read(ch, 1, 1);
799 return !eof && read != 0;
807 bool Puts(char * string)
812 bool Seek(int pos, FileSeekMode mode)
814 if(mode == start && bufferPos == 0 && pos <= bufferCount & pos >= 0)
819 else if(mode == current && bufferPos == 0 && (position + pos) <= bufferCount & (position + pos) >= 0)
821 bufferPos = position + pos;
824 else if(mode == end && totalSizeSet && bufferPos == 0 && bufferCount == totalSize && (totalSize - pos) <= bufferCount & (totalSize - pos) >= 0)
826 bufferPos = totalSize - pos;
855 HTTPConnection connection;
868 byte buffer[HTTPFILE_BUFFERSIZE];
875 String contentDisposition;
878 public HTTPFile FileOpenURL(char * name)
881 if(f.OpenURL(name, null, null))