8 class ConnectionsHolder
10 List<HTTPConnection> connections { };
15 while((c = connections[0]))
16 delete c; // The HTTPConnection destructor will take out from the list
20 static ConnectionsHolder holder { };
24 /*static */define HTTPFILE_BUFFERSIZE = 65536;
26 static Mutex connectionsMutex { };
28 static class ServerNode : BTNode
34 char * name = (char *)key;
39 static class ServerNameCache
43 CompareKey = (void *)BinaryTree::CompareString;
50 while((server = (ServerNode)servers.root))
52 servers.Remove(server);
56 bool Resolve(char * host, char * address)
60 server = (ServerNode)servers.FindString(host);
63 server = ServerNode { key = (uintptr)CopyString(host) };
65 server.resolved = GetAddressFromName(host, server.address);
69 strcpy(address, server.address);
70 return server.resolved;
74 static ServerNameCache serverNameCache { };
76 static char * GetString(char * string, char * what, int count)
79 for(c = 0; what[c]; c++)
81 if((count && c >= count) || (string[c] != what[c] && tolower(string[c]) != tolower(what[c])))
87 private class HTTPConnection : Socket
98 // printf("Before TakeOut we have %d connections:\n", holder.connections.count); for(c : holder.connections) ::PrintLn(c.server); ::PrintLn("");
99 holder.connections.TakeOut(this);
101 PrintLn(server, " Connection Closed (", holder.connections.count, ") opened");
102 printf("Now we have %d connections:\n", holder.connections.count);
103 for(c : holder.connections)
112 void OnDisconnect(int code)
114 connectionsMutex.Wait();
116 delete file.connection; // This decrements the file's reference
118 connectionsMutex.Release();
120 // PrintLn(server, " Disconnected Us");
122 delete this; // The 'connections' reference
125 uint Open_OnReceive(const byte * buffer, uint count)
127 HTTPFile file = this.file;
132 bool gotEndLine = false;
133 for(c = 0; c<(int)count-1; c++)
135 if(buffer[c] == '\r' && buffer[c+1] == '\n')
146 char * string = (char *)buffer;
149 fwrite(buffer, 1, c, stdout);
155 //if(file.openStarted)
160 //file.openStarted = true;
161 if((string = GetString((char *)buffer, "HTTP/1.1 ", count)) ||
162 (string = GetString((char *)buffer, "HTTP/1.0 ", count)))
164 file.status = atoi(string);
166 else if(string = GetString((char *)buffer, "Transfer-Encoding: ", count))
168 if(!strnicmp(string, "chunked", strlen("chunked")))
173 else if(string = GetString((char *)buffer, "Content-Length: ", count))
175 file.totalSize = atoi(string);
176 file.totalSizeSet = true;
178 else if(string = GetString((char *)buffer, "Content-Type: ", count))
180 char * cr = strstr(string, "\r");
181 char * lf = strstr(string, "\n");
188 len = strlen(string);
190 file.contentType = new char[len+1];
191 memcpy(file.contentType, string, len);
192 file.contentType[len] = 0;
194 else if(string = GetString((char *)buffer, "Content-disposition: ", count))
196 char * cr = strstr(string, "\r");
197 char * lf = strstr(string, "\n");
204 len = strlen(string);
206 file.contentDisposition = new char[len+1];
207 memcpy(file.contentDisposition, string, len);
208 file.contentDisposition[len] = 0;
210 else if(string = GetString((char *)buffer, "Connection: ", count))
212 if(!strnicmp(string, "close", strlen("close")))
217 else if(string = GetString((char *)buffer, "Location: ", count))
221 strncpy(file.relocation, buffer + 10, c - 10);
222 file.relocation[c - 10] = '\0';
227 if(buffer[c] == '\r') c++;
228 if(buffer[c] == '\n') c++;
241 uint Read_OnReceive(const byte * buffer, uint count)
243 HTTPFile file = this.file;
247 if(file.chunked && !file.chunkSize)
250 char * string = null;
252 while(pos + c < (int)count-3)
254 if(buffer[pos+c] == '\r' && buffer[pos+c+1] == '\n')
260 if(buffer[pos] == '\r' && buffer[pos+1] =='\n') pos+= 2;
272 string = buffer + pos;
278 file.chunkSize = strtol(string, null, 16);
281 file.connection.file = null;
282 delete file.connection; // This decrements the file's reference
288 read = Min(count, HTTPFILE_BUFFERSIZE - file.bufferCount);
291 read = Min(read, file.chunkSize);
292 file.chunkSize -= read;
296 memcpy(file.buffer + file.bufferCount, buffer, read);
297 file.bufferCount += read;
306 public class HTTPFile : File
308 bool reuseConnection;
309 reuseConnection = true;
311 property bool reuseConnection
313 set { reuseConnection = value; }
314 get { return reuseConnection; }
316 property String contentType
318 get { return contentType; }
320 property String contentDisposition
322 get { return contentDisposition; }
325 bool OpenURL(char * name, char * referer, char * relocation)
327 return RetrieveHead(name, referer, relocation, false);
332 bool RetrieveHead(char * name, char * referer, char * relocation, bool askBody)
336 if(!this || !name) return false;
337 http = strstr(name, "http://");
338 if(http != name) http = null;
344 delete contentDisposition;
345 // ::PrintLn("Opening ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
351 char * serverStart = ecere::net::GetString(name, "http://", 0);
352 char * fileName = strstr(serverStart, "/");
356 HTTPConnection connection = null;
362 memcpy(server, serverStart, fileName - serverStart - 1);
363 server[fileName - serverStart - 1] = '\0';
366 strcpy(server, serverStart);
368 if(relocation && !fileName && name[strlen(name)-1] != '/')
370 strcpy(relocation, "http://");
371 strcat(relocation, server);
372 strcat(relocation, "/");
375 colon = strchr(server, ':');
378 port = atoi(colon+1);
382 connectionsMutex.Wait();
385 this.connection.file = null;
387 this.connection.Disconnect(0);
388 delete this.connection;
393 while(this.connection && this.connection.connected && this.connection.file)
395 connectionsMutex.Release();
396 this.connection.Process();
397 connectionsMutex.Wait();
408 for(c : holder.connections)
410 if(!strcmpi(c.server, server) && c.port == port)
412 if(!c.file && c.connected)
414 connection = c; // TOFIX: 'incref c' doesn't work
415 incref connection; // HTTPFile reference if we keep it
416 connectionsMutex.Release();
417 connection.ProcessTimeOut(0.000001);
418 connectionsMutex.Wait();
419 if(!connection.connected || connection.file)
421 // We're disconnected or reused already...
432 // ::PrintLn("Reusing Connection ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
434 connection.file = this;
441 char ipAddress[1024];
442 connection = HTTPConnection { };
443 incref connection; // HTTPFile reference on success
445 connection.file = this;
447 connectionsMutex.Release();
449 if(serverNameCache.Resolve(server, ipAddress))
451 // ::PrintLn("No Connection - Connecting ", (uint64)connection, " for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
452 if(connection.Connect(ipAddress /*server*/, port))
454 //::PrintLn("Successfully Connected for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
455 //::PrintLn("Waiting on connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
456 connectionsMutex.Wait();
458 connection.server = CopyString(server);
459 connection.port = port;
461 //::PrintLn("Got connectionsMutex for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
462 holder.connections.Add(connection);
464 printf("Now we have %d connections:\n", holder.connections.count);
465 for(c : holder.connections)
473 incref connection; // Global List Reference
477 // ::PrintLn("Connection Failed for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
478 connectionsMutex.Wait();
484 connectionsMutex.Wait();
491 incref connection; // local reference
493 connection.OnReceive = HTTPConnection::Open_OnReceive;
494 connection.file = this;
495 this.connection = connection;
496 this.relocation = relocation;
497 //openStarted = false;
499 totalSizeSet = false; // HEAD will sometimes give you 0!
500 strcpy(msg, askBody ? "GET /" : "HEAD /");
507 for(c = 0; (ch = fileName[c]); c++)
509 if(ch <= 32 || ch > 128)
513 nibble = (ch & 0xF0) >> 4;
514 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
516 msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0');
524 strcat(msg, " HTTP/1.1\r\nHost: ");
525 //strcat(msg, " HTTP/1.0\r\nHost: ");
528 strcat(msg, "Accept-Charset: UTF-8\r\n");
529 //strcat(msg, "Accept-Charset: ISO-8859-1\r\n");
530 strcat(msg, "Connection: Keep-Alive\r\n");
533 strcat(msg, "Referer: ");
534 strcat(msg, referer);
540 //::PrintLn("Releasing connectionsMutex before GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID());
541 connectionsMutex.Release();
543 // ::PrintLn("Sending GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
544 connection.Send(msg, len);
546 while(this.connection && this.connection.connected && !done)
548 this.connection.Process();
550 //::PrintLn("Got DONE for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n");
557 location = CopyString(name);
559 if(status == 200 || (!status && totalSizeSet))
562 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
565 connectionsMutex.Wait();
574 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
581 // First time check if we already have bytes, second time wait for an event
582 this.connection.Process();
587 else if(totalSizeSet)
590 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
591 while(this.connection && this.connection.connected && position < totalSize)
593 connection.Process();
594 position += bufferCount;
600 connectionsMutex.Wait();
603 this.connection.OnReceive = null;
604 this.connection.file = null;
608 this.connection.Disconnect(0);
614 delete this.connection; // This decrements the file's reference
615 this.relocation = null;
617 totalSizeSet = false;
628 connectionsMutex.Wait();
630 if(reuse && !status && connection && !connection.connected)
639 connectionsMutex.Release();
648 delete contentDisposition;
650 connectionsMutex.Wait();
653 if(totalSizeSet && askedBody)
656 this.connection.OnReceive = HTTPConnection::Read_OnReceive;
657 while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize)
659 connectionsMutex.Release();
660 connection.Process();
661 connectionsMutex.Wait();
662 position += bufferCount;
670 connection.file = null;
672 connection.Disconnect(0);
676 connectionsMutex.Release();
680 while(connection && connection.connected && connection.file)
682 connectionsMutex.Release();
683 connection.Process();
684 connectionsMutex.Wait();
687 //::PrintLn("Done with ", (uint64)this);
691 int Read(byte * buffer, uint size, uint count)
693 uint readSize = size * count;
696 Time lastTime = GetTime();
701 if(!RetrieveHead(this.location, null, null, true))
705 if(totalSizeSet && position >= totalSize)
707 while(!eof && read < readSize && !aborted)
709 uint numbytes = bufferCount - bufferPos;
710 numbytes = Min(numbytes, readSize - read);
712 numbytes = Min(numbytes, totalSize - position);
716 lastTime = GetTime();
717 memcpy(buffer + read, this.buffer + bufferPos, numbytes);
718 bufferPos += numbytes;
719 position += numbytes;
722 lastTime = GetTime();
724 if(bufferPos > HTTPFILE_BUFFERSIZE / 2)
726 // Shift bytes back to beginning of buffer
727 uint shift = bufferCount - bufferPos;
729 memcpy(this.buffer, this.buffer + bufferPos, shift);
730 bufferCount -= bufferPos;
736 if(!connection || !connection.connected)
740 // First time check if we already have bytes, second time wait for an event
741 connection.Process();
742 if(wait && bufferCount - bufferPos == 0 && GetTime() - lastTime > 5)
747 if(totalSizeSet && position >= totalSize)
753 int Write(byte * buffer, uint size, uint count)
760 int read = Read(ch, 1, 1);
761 return !eof && read != 0;
769 bool Puts(char * string)
774 bool Seek(int pos, FileSeekMode mode)
776 if(mode == start && bufferPos == 0 && pos <= bufferCount & pos >= 0)
781 else if(mode == current && bufferPos == 0 && (position + pos) <= bufferCount & (position + pos) >= 0)
783 bufferPos = position + pos;
786 else if(mode == end && totalSizeSet && bufferPos == 0 && bufferCount == totalSize && (totalSize - pos) <= bufferCount & (totalSize - pos) >= 0)
788 bufferPos = totalSize - pos;
817 HTTPConnection connection;
830 byte buffer[HTTPFILE_BUFFERSIZE];
837 String contentDisposition;
840 public HTTPFile FileOpenURL(char * name)
843 if(f.OpenURL(name, null, null))