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, 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;
290 delete file.connection; // This decrements the file's reference
296 read = Min(count, HTTPFILE_BUFFERSIZE - file.bufferCount);
299 read = Min(read, file.chunkSize);
300 file.chunkSize -= read;
304 memcpy(file.buffer + file.bufferCount, buffer, read);
305 file.bufferCount += read;
314 public class HTTPFile : File
316 bool reuseConnection;
317 reuseConnection = true;
319 property bool reuseConnection
321 set { reuseConnection = value; }
322 get { return reuseConnection; }
324 property String contentType
326 get { return contentType; }
328 property String contentDisposition
330 get { return contentDisposition; }
333 bool OpenURL(char * name, char * referer, char * relocation)
335 return RetrieveHead(name, referer, relocation, false);
340 bool RetrieveHead(char * name, char * referer, char * relocation, bool askBody)
344 if(!this || !name) return false;
345 http = strstr(name, "http://");
346 if(http != name) http = null;
347 https = http ? null : strstr(name, "https://"); if(https && https != name) https = null;
353 delete contentDisposition;
354 // ::PrintLn("Opening ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
355 if(this && (http || https))
360 char * serverStart = http ? ecere::net::GetString(name, "http://", 0) : ecere::net::GetString(name, "https://", 0);
361 char * fileName = strstr(serverStart, "/");
362 int port = https ? 443 : 80;
365 HTTPConnection connection = null;
371 memcpy(server, serverStart, fileName - serverStart - 1);
372 server[fileName - serverStart - 1] = '\0';
375 strcpy(server, serverStart);
377 if(relocation && !fileName && name[strlen(name)-1] != '/')
379 strcpy(relocation, http ? "http://" : "https://");
380 strcat(relocation, server);
381 strcat(relocation, "/");
384 colon = strchr(server, ':');
387 port = atoi(colon+1);
391 connectionsMutex.Wait();
394 this.connection.file = null;
396 this.connection.Disconnect(0);
397 delete this.connection;
402 while(this.connection && this.connection.connected && this.connection.file)
404 connectionsMutex.Release();
405 this.connection.Process();
406 connectionsMutex.Wait();
417 for(c : holder.connections)
419 if(!strcmpi(c.server, server) && c.port == port && c.secure == (https ? true : false))
421 if(!c.file && c.connected)
423 connection = c; // TOFIX: 'incref c' doesn't work
424 incref connection; // HTTPFile reference if we keep it
425 connectionsMutex.Release();
426 connection.ProcessTimeOut(0.000001);
427 connectionsMutex.Wait();
428 if(!connection.connected || connection.file)
430 // We're disconnected or reused already...
441 // ::PrintLn("Reusing Connection ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
443 connection.file = this;
450 char ipAddress[1024];
451 connection = HTTPConnection
454 autoEstablish = https ? true : false
457 incref connection; // HTTPFile reference on success
459 connection.file = this;
461 connectionsMutex.Release();
463 if(serverNameCache.Resolve(server, ipAddress))
465 // ::PrintLn("No Connection - Connecting ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
466 if(connection.Connect(ipAddress /*server*/, port))
468 //::PrintLn("Successfully Connected for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
469 //::PrintLn("Waiting on connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
470 connectionsMutex.Wait();
472 connection.server = CopyString(server);
473 connection.port = port;
474 connection.secure = https ? true : false;
476 //::PrintLn("Got connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
477 holder.connections.Add(connection);
479 printf("Now we have %d connections:\n", holder.connections.count);
480 for(c : holder.connections)
488 incref connection; // Global List Reference
492 // ::PrintLn("Connection Failed for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
493 connectionsMutex.Wait();
499 connectionsMutex.Wait();
506 incref connection; // local reference
508 connection.OnReceive = HTTPConnection::Open_OnReceive;
509 connection.file = this;
510 this.connection = connection;
511 this.relocation = relocation;
512 //openStarted = false;
514 totalSizeSet = false; // HEAD will sometimes give you 0!
515 strcpy(msg, askBody ? "GET /" : "HEAD /");
522 for(c = 0; (ch = fileName[c]); c++)
524 if(ch <= 32 || ch > 128)
528 nibble = (ch & 0xF0) >> 4;
529 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
531 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
539 strcat(msg, " HTTP/1.1\r\nHost: ");
540 //strcat(msg, " HTTP/1.0\r\nHost: ");
543 strcat(msg, "Accept-Charset: UTF-8\r\n");
544 //strcat(msg, "Accept-Charset: ISO-8859-1\r\n");
545 strcat(msg, "Connection: Keep-Alive\r\n");
548 strcat(msg, "Referer: ");
549 strcat(msg, referer);
555 //::PrintLn("Releasing connectionsMutex before GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
556 connectionsMutex.Release();
558 // ::PrintLn("Sending GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
559 connection.Send(msg, len);
561 while(this.connection && this.connection.connected && !done)
563 this.connection.Process();
565 //::PrintLn("Got DONE for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
572 location = CopyString(name);
574 if(status == 200 || (!status && totalSizeSet))
577 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
580 connectionsMutex.Wait();
589 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
596 // First time check if we already have bytes, second time wait for an event
597 this.connection.Process();
602 else if(totalSizeSet)
605 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
606 while(this.connection && this.connection.connected && position < totalSize)
608 connection.Process();
609 position += bufferCount;
615 connectionsMutex.Wait();
618 this.connection.OnReceive = null;
619 this.connection.file = null;
623 this.connection.Disconnect(0);
629 delete this.connection; // This decrements the file's reference
630 this.relocation = null;
632 totalSizeSet = false;
643 connectionsMutex.Wait();
645 if(reuse && !status && connection && !connection.connected)
654 connectionsMutex.Release();
663 delete contentDisposition;
665 connectionsMutex.Wait();
668 if(totalSizeSet && askedBody)
671 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
672 while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize)
674 connectionsMutex.Release();
675 connection.Process();
676 connectionsMutex.Wait();
677 position += bufferCount;
685 connection.file = null;
687 connection.Disconnect(0);
691 connectionsMutex.Release();
695 while(connection && connection.connected && connection.file)
697 connectionsMutex.Release();
698 connection.Process();
699 connectionsMutex.Wait();
702 //::PrintLn("Done with ", (uint64)this);
706 int Read(byte * buffer, uint size, uint count)
708 uint readSize = size * count;
711 Time lastTime = GetTime();
716 if(!RetrieveHead(this.location, null, null, true))
720 if(totalSizeSet && position >= totalSize)
722 while(!eof && read < readSize && !aborted)
724 uint numbytes = bufferCount - bufferPos;
725 numbytes = Min(numbytes, readSize - read);
727 numbytes = Min(numbytes, totalSize - position);
731 lastTime = GetTime();
732 memcpy(buffer + read, this.buffer + bufferPos, numbytes);
733 bufferPos += numbytes;
734 position += numbytes;
737 lastTime = GetTime();
739 if(bufferPos > HTTPFILE_BUFFERSIZE / 2)
741 // Shift bytes back to beginning of buffer
742 uint shift = bufferCount - bufferPos;
744 memcpy(this.buffer, this.buffer + bufferPos, shift);
745 bufferCount -= bufferPos;
751 if(!connection || !connection.connected)
755 // First time check if we already have bytes, second time wait for an event
756 connection.Process();
757 if(wait && bufferCount - bufferPos == 0 && GetTime() - lastTime > 5)
762 if(totalSizeSet && position >= totalSize)
768 int Write(byte * buffer, uint size, uint count)
775 int read = Read(ch, 1, 1);
776 return !eof && read != 0;
784 bool Puts(char * string)
789 bool Seek(int pos, FileSeekMode mode)
791 if(mode == start && bufferPos == 0 && pos <= bufferCount & pos >= 0)
796 else if(mode == current && bufferPos == 0 && (position + pos) <= bufferCount & (position + pos) >= 0)
798 bufferPos = position + pos;
801 else if(mode == end && totalSizeSet && bufferPos == 0 && bufferCount == totalSize && (totalSize - pos) <= bufferCount & (totalSize - pos) >= 0)
803 bufferPos = totalSize - pos;
832 HTTPConnection connection;
845 byte buffer[HTTPFILE_BUFFERSIZE];
852 String contentDisposition;
855 public HTTPFile FileOpenURL(char * name)
858 if(f.OpenURL(name, null, null))