IdTestHttp.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. unit IdTestHTTP;
  2. {
  3. http://www.schroepl.net/cgi-bin/http_trace.pl
  4. useful to test exactly what headers a web server is receiving from the http client
  5. allows you to check that the headers that http client writes are actually received
  6. by the server, ie not modified by a proxy/firewall after they leave your pc
  7. }
  8. //todo test chunked Transfer-Encoding
  9. //http://www.faqs.org/rfcs/rfc2616.html
  10. //3.6 Transfer Codings, 19.4.6 Introduction of Transfer-Encoding
  11. //todo standardize http ports used
  12. {$I IdCompilerDefines.inc}
  13. interface
  14. //short term solution to allow trying alternate compression implementations
  15. {.$DEFINE INDY_USE_ABBREVIA}
  16. uses
  17. {$IFDEF INDY_USE_ABBREVIA}
  18. IdCompressorAbbrevia,
  19. {$ENDIF}
  20. {$IFNDEF DOTNET}
  21. IdCompressorZLibEx,
  22. {$ENDIF}
  23. IdZLibCompressorBase,
  24. IdObjs,
  25. IdLogDebug,
  26. IdCoder,
  27. IdCoderMime,
  28. IdSys,
  29. IdGlobal,
  30. IdContext,
  31. IdTCPServer,
  32. IdThreadSafe,
  33. IdCustomHTTPServer,
  34. IdHTTPServer,
  35. IdTest,
  36. IdHTTP;
  37. type
  38. {
  39. GZip compression tested using apache 2.0
  40. add following to httpd.conf to enable gzip on given types
  41. LoadModule deflate_module modules/mod_deflate.so
  42. AddOutputFilterByType DEFLATE text/html text/plain text/xml
  43. }
  44. {
  45. can test if a compressed stream is valid by renaming to .gz and
  46. opening with compression utility, eg winrar
  47. see also IdCompressorAbbrevia for a reimplementation
  48. }
  49. //Note that you can NOT do this test at all in DotNET because
  50. //we currently have no Indy code for it.
  51. {$IFNDEF DOTNET}
  52. TIdTestHTTP_GZip = class(TIdTest)
  53. private
  54. procedure CallbackGet(AContext:TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  55. published
  56. procedure TestCompress;
  57. procedure TestGZip;
  58. end;
  59. {$ENDIF}
  60. //http://www.io.com/~maus/HttpKeepAlive.html
  61. //re keep-alive, see TIdHTTPResponseInfo.WriteHeader. old comment, delete?
  62. TIdTestHTTP_KeepAlive = class(TIdTest)
  63. private
  64. FRequestCount:TIdThreadSafeInteger;
  65. FServerConnectCount:TIdThreadSafeInteger;
  66. procedure CallbackConnect(AContext:TIdContext);
  67. //procedure CallbackExecute(AContext:TIdContext);
  68. procedure CallbackGet(AContext:TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  69. published
  70. //this currently seems unreliable. sometimes server will complete the
  71. //request, but client returns empty comment
  72. procedure TestKeepAlive;
  73. end;
  74. //tests that redirection commands are followed properly
  75. TIdTestHTTP_Redirect = class(TIdTest)
  76. private
  77. procedure CallbackGet(AContext:TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  78. published
  79. procedure TestRedirect;
  80. end;
  81. //basic subclass that keeps statistics
  82. TIdHTTPStats = class(TIdHTTP)
  83. protected
  84. procedure DoOnConnected;override;
  85. public
  86. //record actual tcp connections
  87. CountConnect:Integer;
  88. end;
  89. TIdHTTPServerStats = class(TIdHTTPServer)
  90. end;
  91. //this test shows a memory leak due to AResponse.KeepAlive being called in a
  92. //finally block, and raising an exception.
  93. //seperate test class as it has private callbacks for the server
  94. //the actual result of this test (a memory leak) will only properly be detected
  95. //on application shutdown, when running an appropriate memory checker
  96. TIdTestHTTP_01 = class(TIdTest)
  97. private
  98. procedure CallbackExecute(AContext:TIdContext);
  99. published
  100. procedure TestMemoryLeak;
  101. end;
  102. TIdTestHTTP_02 = class(TIdTest)
  103. private
  104. FServer:TIdHTTPServer;
  105. FClient:TIdHTTP;
  106. procedure CallbackGet(AContext:TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  107. protected
  108. procedure SetUp;override;
  109. procedure TearDown;override;
  110. published
  111. procedure TestRedirectedDownload;
  112. end;
  113. implementation
  114. uses IdBaseComponent;
  115. const
  116. //base64 encoding of gzipped 'hello world'
  117. //used to test decompression
  118. cHelloWorldGz='H4sIAAAAAAAAC8tIzcnJVyjPL8pJAQCFEUoNCwAAAA==';
  119. //and what it decodes to
  120. cHelloWorld='hello world';
  121. //document on the server to ask for
  122. cDocGZip='gz';
  123. //content encoding constant
  124. cEncodingGZip='gzip';
  125. cRedirectData='data';
  126. procedure TIdTestHTTP_01.CallbackExecute(AContext: TIdContext);
  127. //check that the server disconnecting the client doesn't cause
  128. //issues, eg memory leaks
  129. begin
  130. AContext.Connection.Disconnect;
  131. end;
  132. procedure TIdTestHTTP_01.TestMemoryLeak;
  133. var
  134. aClient:TIdHTTP;
  135. aServer:TIdTCPServer;
  136. begin
  137. aClient:=TIdHTTP.Create(nil);
  138. aServer:=TIdTCPServer.Create(nil);
  139. try
  140. aServer.DefaultPort:=20202;
  141. aServer.OnExecute:=Self.CallbackExecute;
  142. aServer.Active:=True;
  143. //the circumstances for the memory leak also require a ConnectionTimeout
  144. //haven't investigated why
  145. aClient.ConnectTimeout:=2000;
  146. try
  147. aClient.Get('http://127.0.0.1:20202');
  148. except
  149. //we're expecting this exception, ignore it
  150. end;
  151. finally
  152. Sys.FreeAndNil(aClient);
  153. Sys.FreeAndNil(aServer);
  154. end;
  155. end;
  156. procedure TIdTestHTTP_KeepAlive.CallbackConnect(AContext: TIdContext);
  157. begin
  158. FServerConnectCount.Increment;
  159. end;
  160. procedure TIdTestHTTP_KeepAlive.CallbackGet(AContext: TIdContext;
  161. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  162. begin
  163. FRequestCount.Increment;
  164. if ARequestInfo.Document='/1' then AResponseInfo.ContentText:='a'
  165. else if ARequestInfo.Document='/2' then AResponseInfo.ContentText:='b'
  166. else AResponseInfo.ContentText:='Unknown:'+ARequestInfo.Document;
  167. end;
  168. procedure TIdTestHTTP_KeepAlive.TestKeepAlive;
  169. //in order to test that keepAlive is working, should count the connection
  170. //attempts on client and server. repeat for keepAlive=true/false
  171. //http client seems to be biased towards 1.1 (see TIdCustomHTTP.ConnectToHost),
  172. //giving no keep-alive, but server is 1.0, expecting a keep-alive
  173. //a fast-response, high requestcount keep-alive client could expect
  174. //a 3x time reduction for the session
  175. var
  176. c:TIdHTTPStats;
  177. aContent:string;
  178. aServer:TIdHTTPServer;
  179. const
  180. cUrl='http://127.0.0.1:22280/';
  181. begin
  182. FServerConnectCount:=TIdThreadSafeInteger.Create;
  183. FRequestCount:=TIdThreadSafeInteger.Create;
  184. aServer:=TIdHTTPServer.Create;
  185. c:=TIdHTTPStats.Create;
  186. try
  187. aServer.OnConnect:=CallbackConnect;
  188. aServer.OnCommandGet:=CallbackGet;
  189. //server currently like 1.0, requires explicit keep-alive request
  190. aServer.KeepAlive:=True;
  191. aServer.DefaultPort:=22280;
  192. aServer.Active:=True;
  193. //seems to be the only place to specify on client
  194. c.Request.Connection:='keep-alive';
  195. //ensure content is different+correct for each request
  196. aContent:=c.Get(cUrl+'1');
  197. Assert(aContent='a',aContent);
  198. aContent:=c.Get(cUrl+'2');
  199. Assert(aContent='b',aContent);
  200. Assert(c.CountConnect=1);
  201. Assert(FServerConnectCount.Value=1);
  202. Assert(FRequestCount.Value=2);
  203. finally
  204. Sys.FreeAndNil(c);
  205. Sys.FreeAndNil(aServer);
  206. Sys.FreeAndNil(FServerConnectCount);
  207. Sys.FreeAndNil(FRequestCount);
  208. end;
  209. end;
  210. procedure TIdHTTPStats.DoOnConnected;
  211. begin
  212. inherited;
  213. Inc(CountConnect);
  214. end;
  215. procedure TIdTestHTTP_Redirect.CallbackGet(AContext: TIdContext;
  216. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  217. begin
  218. if ARequestInfo.Document='/1' then AResponseInfo.Redirect('/2')
  219. else if ARequestInfo.Document='/2' then AResponseInfo.ContentText:='b';
  220. end;
  221. procedure TIdTestHTTP_Redirect.TestRedirect;
  222. var
  223. aClient:TIdHTTP;
  224. aServer:TIdHTTPServer;
  225. s:string;
  226. aCorrectClass:boolean;
  227. aClassName:string;
  228. begin
  229. aClient:=TIdHTTP.Create;
  230. aServer:=TIdHTTPServer.Create;
  231. try
  232. aServer.OnCommandGet:=CallbackGet;
  233. aServer.DefaultPort:=22280;
  234. aServer.Active:=True;
  235. //this shouldnt be needed
  236. aClient.ReadTimeout:=10000;
  237. //first, try without redirects enabled. should get an exception.
  238. aClient.HandleRedirects:=False;
  239. try
  240. aCorrectClass:=True;
  241. s:=aClient.Get('http://127.0.0.1:22280/1');
  242. Assert(False);//should not get here
  243. except
  244. //expect to get here
  245. //check exception class. dont raise exception in except-block.
  246. on e:Exception do
  247. begin
  248. aClassName:=e.ClassName;
  249. aCorrectClass:=e is EIdHTTPProtocolException;
  250. end;
  251. end;
  252. //also check code (302)
  253. Assert(aCorrectClass,aClassName);
  254. //now let indy handle the redirection instructions from the server
  255. aClient.HandleRedirects:=True;
  256. s:=aClient.Get('http://127.0.0.1:22280/1');
  257. Assert(s='b',s);
  258. Assert(aClient.RedirectCount=1);
  259. //todo test that RedirectMaximum is followed
  260. finally
  261. Sys.FreeAndNil(aClient);
  262. Sys.FreeAndNil(aServer);
  263. end;
  264. end;
  265. {
  266. //use to encode test data so it can be included in source easily
  267. aEncode:=TIdEncoderMIME.Create;
  268. aStream:=TIdMemoryStream.Create;
  269. try
  270. aStream.LoadFromFile('e:\test.txt');
  271. s:=aEncode.Encode(aStream);
  272. assert(s<>'');
  273. finally
  274. sys.FreeAndNil(aEncode);
  275. end;
  276. Exit;
  277. }
  278. {$IFNDEF DOTNET}
  279. procedure TIdTestHTTP_GZip.CallbackGet(AContext: TIdContext;
  280. ARequestInfo: TIdHTTPRequestInfo;
  281. AResponseInfo: TIdHTTPResponseInfo);
  282. //currently content has to be manually compressed
  283. //eg server doesnt auto-compress all "plain/text" content if client allows
  284. begin
  285. if ARequestInfo.Document='/'+cDocGZip then
  286. begin
  287. //todo reply based on the requests ContentEncoding string
  288. AResponseInfo.ContentEncoding:=cEncodingGZip;
  289. AResponseInfo.ContentText:=DecodeString(TIdDecoderMIME,cHelloWorldGz);
  290. end
  291. else
  292. begin
  293. AResponseInfo.ContentText:='error';
  294. end;
  295. end;
  296. procedure TIdTestHTTP_GZip.TestCompress;
  297. //tests the client correctly decompressed gzip content received from server
  298. //basically the same as TestGZip but with the httpclient+server too
  299. //todo also deflate?
  300. var
  301. aClass:TIdZLibCompressorBaseClass;
  302. aCompress:TIdZLibCompressorBase;
  303. aClient:TIdHTTP;
  304. aStream:TIdStringStream;
  305. aServer:TIdHTTPServer;
  306. begin
  307. {$IFDEF DOTNET}
  308. aClass:=nil;
  309. {$ELSE}
  310. {$IFDEF INDY_USE_ABBREVIA}
  311. aClass:=TIdCompressorAbbrevia;
  312. {$ELSE}
  313. aClass:=TIdCompressorZLibEx;
  314. {$ENDIF}
  315. {$ENDIF}
  316. //todo test that server only returns compressed data if we ask for it
  317. Assert(aClass<>nil);
  318. aCompress:=aClass.Create;
  319. aServer:=TIdHTTPServer.Create;
  320. aClient:=TIdHTTP.Create;
  321. aStream:=TIdStringStream.Create('');
  322. try
  323. aServer.DefaultPort:=22280;
  324. aServer.OnCommandGet:=Self.CallbackGet;
  325. aServer.Active:=True;
  326. aClient.Compressor:=aCompress;
  327. //identity is due to existing code in http unit, may be changed/removed in future.
  328. aClient.Request.AcceptEncoding := 'gzip, deflate, identity';
  329. aClient.CreateIOHandler;
  330. //aClient.HandleRedirects:=True;
  331. //aClient.Request.UserAgent:='';
  332. //can also test using an external web server, eg apache
  333. //aClient.Get('http://192.168.1.100:22280/helloworld.txt',aStream);
  334. aClient.Get('http://127.0.0.1:22280/'+cDocGZip,aStream);
  335. Assert(aStream.DataString='hello world',aClass.ClassName);
  336. //aStream.SaveToFile('e:\test.txt');
  337. finally
  338. Sys.FreeAndNil(aServer);
  339. Sys.FreeAndNil(aStream);
  340. Sys.FreeAndNil(aClient);
  341. Sys.FreeAndNil(aCompress);
  342. end;
  343. end;
  344. procedure TIdTestHTTP_GZip.TestGZip;
  345. //basic gzip functionality test.
  346. //doesn't really belong in this http unit
  347. var
  348. aClass:TIdZLibCompressorBaseClass;
  349. aCompress:TIdZLibCompressorBase;
  350. //dont use stringstream as it acts differently
  351. aStream,aOutStream:TIdMemoryStream;
  352. s:string;
  353. begin
  354. //string now contains a gz encoded test string
  355. s:=DecodeString(TIdDecoderMIME,cHelloWorldGz);
  356. //better way to do? eg iterate and test all registered compression classes?
  357. {$IFDEF DOTNET}
  358. aClass:=nil;
  359. {$ELSE}
  360. {$IFDEF INDY_USE_ABBREVIA}
  361. aClass:=TIdCompressorAbbrevia;
  362. {$ELSE}
  363. aClass:=TIdCompressorZLibEx;
  364. {$ENDIF}
  365. {$ENDIF}
  366. Assert(aClass<>nil);
  367. aStream:=TIdMemoryStream.Create;
  368. WriteStringToStream(aStream,s);
  369. aOutStream:=TIdMemoryStream.Create;
  370. aCompress:=aClass.Create;
  371. try
  372. aStream.Position:=0;
  373. aCompress.DecompressGZipStream(aStream,aOutStream);
  374. aOutStream.Position:=0;
  375. s:=ReadStringFromStream(aOutStream);
  376. Assert(s=cHelloWorld,s);
  377. finally
  378. Sys.FreeAndNil(aCompress);
  379. Sys.FreeAndNil(aStream);
  380. Sys.FreeAndNil(aOutStream);
  381. end;
  382. end;
  383. {$ENDIF}
  384. procedure TIdTestHTTP_02.CallbackGet(AContext: TIdContext;
  385. ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  386. begin
  387. if ARequestInfo.Document='/redirect' then
  388. begin
  389. AResponseInfo.ContentText:='message';
  390. AResponseInfo.Redirect('/download');
  391. end
  392. else if ARequestInfo.Document='/download' then
  393. begin
  394. AResponseInfo.ContentText:=cRedirectData;
  395. end;
  396. end;
  397. procedure TIdTestHTTP_02.SetUp;
  398. begin
  399. inherited;
  400. FServer:=TIdHTTPServer.Create;
  401. FServer.DefaultPort:=22280;
  402. FServer.OnCommandGet:=Self.CallbackGet;
  403. FClient:=TIdHTTP.Create;
  404. end;
  405. procedure TIdTestHTTP_02.TearDown;
  406. begin
  407. Sys.FreeAndNil(FServer);
  408. Sys.FreeAndNil(FClient);
  409. inherited;
  410. end;
  411. procedure TIdTestHTTP_02.TestRedirectedDownload;
  412. //this shows that when requesting a download from a server, content
  413. //from the redirecting page is not included in the data from the
  414. //actual download
  415. var
  416. s:TIdMemoryStream;
  417. aStr:string;
  418. begin
  419. FServer.Active:=True;
  420. s:=TIdMemoryStream.Create;
  421. try
  422. FClient.HandleRedirects:=True;
  423. FClient.Get('http://127.0.0.1:22280/redirect?download',s);
  424. s.Position:=0;
  425. aStr:=ReadStringFromStream(s);
  426. Assert(aStr=cRedirectData);
  427. finally
  428. sys.FreeAndNil(s);
  429. end;
  430. end;
  431. initialization
  432. TIdTest.RegisterTest(TIdTestHTTP_01);
  433. TIdTest.RegisterTest(TIdTestHTTP_02);
  434. TIdTest.RegisterTest(TIdTestHTTP_KeepAlive);
  435. TIdTest.RegisterTest(TIdTestHTTP_Redirect);
  436. {$IFNDEF DOTNET}
  437. TIdTest.RegisterTest(TIdTestHTTP_GZip);
  438. {$ENDIF}
  439. end.