/src/yolk-static_content.adb

http://github.com/ThomasLocke/yolk · Ada · 438 lines · 299 code · 60 blank · 79 comment · 12 complexity · 31e1de1400b8e4521c66b37961761dd1 MD5 · raw file

  1. -------------------------------------------------------------------------------
  2. -- --
  3. -- Copyright (C) 2010-, Thomas ¸cke --
  4. -- --
  5. -- This library is free software; you can redistribute it and/or modify --
  6. -- it under terms of the GNU General Public License as published by the --
  7. -- Free Software Foundation; either version 3, or (at your option) any --
  8. -- later version. This library is distributed in the hope that it will be --
  9. -- useful, but WITHOUT ANY WARRANTY; without even the implied warranty of --
  10. -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. --
  11. -- --
  12. -- As a special exception under Section 7 of GPL version 3, you are --
  13. -- granted additional permissions described in the GCC Runtime Library --
  14. -- Exception, version 3.1, as published by the Free Software Foundation. --
  15. -- --
  16. -- You should have received a copy of the GNU General Public License and --
  17. -- a copy of the GCC Runtime Library Exception along with this program; --
  18. -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see --
  19. -- <http://www.gnu.org/licenses/>. --
  20. -- --
  21. -------------------------------------------------------------------------------
  22. with Ada.Calendar;
  23. with Ada.Directories;
  24. with Ada.Streams.Stream_IO;
  25. with Ada.Strings.Unbounded;
  26. with AWS.MIME;
  27. with ZLib;
  28. with Yolk.Configuration;
  29. with Yolk.Not_Found;
  30. with Yolk.Log;
  31. package body Yolk.Static_Content is
  32. protected GZip_And_Cache is
  33. procedure Do_It
  34. (GZ_Resource : in String;
  35. Resource : in String);
  36. -- If a compressable resource is requested and it doesn't yet exist,
  37. -- then this procedure takes care of GZip'ing the Resource and saving
  38. -- it to disk as a .gz file.
  39. function Get_Cache_Option
  40. return AWS.Messages.Cache_Option;
  41. -- Return the Cache_Option object for the resource.
  42. procedure Initialize;
  43. -- Delete and re-create the Compressed_Cache_Directory.
  44. procedure Set_Cache_Options
  45. (No_Cache : in Boolean := False;
  46. No_Store : in Boolean := False;
  47. No_Transform : in Boolean := False;
  48. Max_Age : in AWS.Messages.Delta_Seconds := 86400;
  49. S_Max_Age : in AWS.Messages.Delta_Seconds :=
  50. AWS.Messages.Unset;
  51. Public : in Boolean := False;
  52. Must_Revalidate : in Boolean := True;
  53. Proxy_Revalidate : in Boolean := False);
  54. -- Set the Cache_Data objects.
  55. private
  56. Cache_Options : Ada.Strings.Unbounded.Unbounded_String;
  57. end GZip_And_Cache;
  58. -- Handle GZip'ing, saving and deletion of compressable resources. This is
  59. -- done in a protected object so we don't get multiple threads all trying
  60. -- to save/delete the same resources at the same time.
  61. function Good_Age
  62. (Resource : in String)
  63. return Boolean;
  64. -- Return True if the age of Resource is younger than the configuration
  65. -- parameter Compressed_Max_Age. If Compressed_Max_Age is 0, then always
  66. -- return True.
  67. --------------------
  68. -- Compressable --
  69. --------------------
  70. function Compressable
  71. (Request : in AWS.Status.Data)
  72. return AWS.Response.Data
  73. is
  74. use Ada.Directories;
  75. use AWS.Messages;
  76. use AWS.Status;
  77. use Yolk.Configuration;
  78. use Yolk.Log;
  79. GZ_Resource : constant String :=
  80. Config.Get (Compressed_Static_Content_Cache)
  81. & URI (Request) & ".gz";
  82. -- The path to the GZipped resource.
  83. Resource : constant String := Config.Get (WWW_Root) & URI (Request);
  84. -- The path to the requested resource.
  85. MIME_Type : constant String := AWS.MIME.Content_Type (Resource);
  86. Minimum_File_Size : constant File_Size :=
  87. File_Size (Integer'(Config.Get
  88. (Compress_Static_Content_Minimum_File_Size)));
  89. begin
  90. if not Exists (Resource)
  91. or else Kind (Resource) /= Ordinary_File
  92. then
  93. return Yolk.Not_Found.Generate (Request);
  94. end if;
  95. if Is_Supported (Request, GZip) then
  96. if Exists (GZ_Resource)
  97. and then Kind (GZ_Resource) = Ordinary_File
  98. then
  99. if Good_Age (Resource => GZ_Resource) then
  100. return AWS.Response.File
  101. (Content_Type => MIME_Type,
  102. Filename => GZ_Resource,
  103. Encoding => GZip,
  104. Cache_Control => GZip_And_Cache.Get_Cache_Option);
  105. else
  106. Delete_File (GZ_Resource);
  107. end if;
  108. elsif Exists (GZ_Resource)
  109. and then Kind (GZ_Resource) /= Ordinary_File
  110. then
  111. -- Not so good. Log to ERROR trace and return un-compressed
  112. -- content.
  113. Trace
  114. (Handle => Error,
  115. Message => GZ_Resource
  116. & " exists and is not an ordinary file");
  117. return AWS.Response.File
  118. (Content_Type => MIME_Type,
  119. Filename => Resource,
  120. Cache_Control => GZip_And_Cache.Get_Cache_Option);
  121. end if;
  122. if Size (Resource) > Minimum_File_Size then
  123. GZip_And_Cache.Do_It (GZ_Resource => GZ_Resource,
  124. Resource => Resource);
  125. return AWS.Response.File
  126. (Content_Type => MIME_Type,
  127. Filename => GZ_Resource,
  128. Encoding => GZip,
  129. Cache_Control => GZip_And_Cache.Get_Cache_Option);
  130. end if;
  131. end if;
  132. return AWS.Response.File
  133. (Content_Type => MIME_Type,
  134. Filename => Resource,
  135. Cache_Control => GZip_And_Cache.Get_Cache_Option);
  136. end Compressable;
  137. ----------------
  138. -- Good_Age --
  139. ----------------
  140. function Good_Age
  141. (Resource : in String)
  142. return Boolean
  143. is
  144. use Ada.Calendar;
  145. use Ada.Directories;
  146. use Yolk.Configuration;
  147. Max_Age : constant Natural :=
  148. Config.Get (Compressed_Static_Content_Max_Age);
  149. begin
  150. if Max_Age = 0 then
  151. return True;
  152. end if;
  153. if
  154. Clock - Modification_Time (Resource) > Duration (Max_Age)
  155. then
  156. return False;
  157. end if;
  158. return True;
  159. end Good_Age;
  160. ----------------------
  161. -- GZip_And_Cache --
  162. ----------------------
  163. protected body GZip_And_Cache is
  164. procedure Do_It
  165. (GZ_Resource : in String;
  166. Resource : in String)
  167. is
  168. use Ada.Directories;
  169. Cache_Dir : constant String := Containing_Directory (GZ_Resource);
  170. begin
  171. if Exists (GZ_Resource) then
  172. return;
  173. -- We only need to continue if the GZ_Resource doesn't exist. It
  174. -- might not have existed when Do_It was called, but the previous
  175. -- Do_It call might've created it. So if it now exists, we simply
  176. -- return.
  177. end if;
  178. if not Exists (Cache_Dir) then
  179. Create_Path (Cache_Dir);
  180. end if;
  181. Compress_File :
  182. declare
  183. File_In : Ada.Streams.Stream_IO.File_Type;
  184. File_Out : Ada.Streams.Stream_IO.File_Type;
  185. Filter : ZLib.Filter_Type;
  186. procedure Data_Read
  187. (Item : out Ada.Streams.Stream_Element_Array;
  188. Last : out Ada.Streams.Stream_Element_Offset);
  189. -- Read data from File_In.
  190. procedure Data_Write
  191. (Item : in Ada.Streams.Stream_Element_Array);
  192. -- Write data to File_Out.
  193. procedure Translate is new ZLib.Generic_Translate
  194. (Data_In => Data_Read,
  195. Data_Out => Data_Write);
  196. -- Do the actual compression. Use Data_Read to read from File_In
  197. -- and Data_Write to write the compressed content to File_Out.
  198. -----------------
  199. -- Data_Read --
  200. -----------------
  201. procedure Data_Read
  202. (Item : out Ada.Streams.Stream_Element_Array;
  203. Last : out Ada.Streams.Stream_Element_Offset)
  204. is
  205. begin
  206. Ada.Streams.Stream_IO.Read
  207. (File => File_In,
  208. Item => Item,
  209. Last => Last);
  210. end Data_Read;
  211. ----------------
  212. -- Data_Out --
  213. ----------------
  214. procedure Data_Write
  215. (Item : in Ada.Streams.Stream_Element_Array)
  216. is
  217. begin
  218. Ada.Streams.Stream_IO.Write (File => File_Out,
  219. Item => Item);
  220. end Data_Write;
  221. begin
  222. Ada.Streams.Stream_IO.Open
  223. (File => File_In,
  224. Mode => Ada.Streams.Stream_IO.In_File,
  225. Name => Resource);
  226. Ada.Streams.Stream_IO.Create
  227. (File => File_Out,
  228. Mode => Ada.Streams.Stream_IO.Out_File,
  229. Name => GZ_Resource);
  230. ZLib.Deflate_Init
  231. (Filter => Filter,
  232. Level => ZLib.Best_Compression,
  233. Header => ZLib.GZip);
  234. Translate (Filter);
  235. ZLib.Close (Filter);
  236. Ada.Streams.Stream_IO.Close (File => File_In);
  237. Ada.Streams.Stream_IO.Close (File => File_Out);
  238. end Compress_File;
  239. end Do_It;
  240. ------------------------
  241. -- Get_Cache_Option --
  242. ------------------------
  243. function Get_Cache_Option
  244. return AWS.Messages.Cache_Option
  245. is
  246. use Ada.Strings.Unbounded;
  247. begin
  248. return AWS.Messages.Cache_Option (To_String (Cache_Options));
  249. end Get_Cache_Option;
  250. ------------------
  251. -- Initialize --
  252. ------------------
  253. procedure Initialize
  254. is
  255. use Ada.Directories;
  256. use Yolk.Configuration;
  257. use Yolk.Log;
  258. begin
  259. if Exists (Config.Get (Compressed_Static_Content_Cache))
  260. and then
  261. Kind (Config.Get (Compressed_Static_Content_Cache)) = Directory
  262. then
  263. Delete_Tree
  264. (Directory => Config.Get (Compressed_Static_Content_Cache));
  265. Trace (Info,
  266. Config.Get (Compressed_Static_Content_Cache)
  267. & " found and deleted");
  268. end if;
  269. Create_Path
  270. (New_Directory => Config.Get (Compressed_Static_Content_Cache));
  271. Trace (Info,
  272. Config.Get (Compressed_Static_Content_Cache)
  273. & " created");
  274. end Initialize;
  275. -------------------------
  276. -- Set_Cache_Options --
  277. -------------------------
  278. procedure Set_Cache_Options
  279. (No_Cache : in Boolean := False;
  280. No_Store : in Boolean := False;
  281. No_Transform : in Boolean := False;
  282. Max_Age : in AWS.Messages.Delta_Seconds := 86400;
  283. S_Max_Age : in AWS.Messages.Delta_Seconds :=
  284. AWS.Messages.Unset;
  285. Public : in Boolean := False;
  286. Must_Revalidate : in Boolean := True;
  287. Proxy_Revalidate : in Boolean := False)
  288. is
  289. use Ada.Strings.Unbounded;
  290. Cache_Data : AWS.Messages.Cache_Data (CKind => AWS.Messages.Response);
  291. begin
  292. Cache_Data.No_Cache := No_Cache;
  293. Cache_Data.No_Store := No_Store;
  294. Cache_Data.No_Transform := No_Transform;
  295. Cache_Data.Max_Age := Max_Age;
  296. Cache_Data.S_Max_Age := S_Max_Age;
  297. Cache_Data.Public := Public;
  298. Cache_Data.Must_Revalidate := Must_Revalidate;
  299. Cache_Data.Proxy_Revalidate := Proxy_Revalidate;
  300. Cache_Options := To_Unbounded_String
  301. (String (AWS.Messages.To_Cache_Option (Cache_Data)));
  302. end Set_Cache_Options;
  303. end GZip_And_Cache;
  304. ------------------------
  305. -- Non_Compressable --
  306. ------------------------
  307. function Non_Compressable
  308. (Request : in AWS.Status.Data)
  309. return AWS.Response.Data
  310. is
  311. use Ada.Directories;
  312. use AWS.MIME;
  313. use AWS.Status;
  314. use Yolk.Configuration;
  315. Resource : constant String := Config.Get (WWW_Root) & URI (Request);
  316. -- The path to the requested resource.
  317. begin
  318. if not Exists (Resource)
  319. or else Kind (Resource) /= Ordinary_File
  320. then
  321. return Yolk.Not_Found.Generate (Request);
  322. end if;
  323. return AWS.Response.File
  324. (Content_Type => Content_Type (Resource),
  325. Filename => Resource,
  326. Cache_Control => GZip_And_Cache.Get_Cache_Option);
  327. end Non_Compressable;
  328. -------------------------
  329. -- Set_Cache_Options --
  330. -------------------------
  331. procedure Set_Cache_Options
  332. (No_Cache : in Boolean := False;
  333. No_Store : in Boolean := False;
  334. No_Transform : in Boolean := False;
  335. Max_Age : in AWS.Messages.Delta_Seconds := 86400;
  336. S_Max_Age : in AWS.Messages.Delta_Seconds := AWS.Messages.Unset;
  337. Public : in Boolean := False;
  338. Must_Revalidate : in Boolean := True;
  339. Proxy_Revalidate : in Boolean := False)
  340. is
  341. begin
  342. GZip_And_Cache.Set_Cache_Options
  343. (No_Cache => No_Cache,
  344. No_Store => No_Store,
  345. No_Transform => No_Transform,
  346. Max_Age => Max_Age,
  347. S_Max_Age => S_Max_Age,
  348. Public => Public,
  349. Must_Revalidate => Must_Revalidate,
  350. Proxy_Revalidate => Proxy_Revalidate);
  351. end Set_Cache_Options;
  352. ----------------------------------
  353. -- Static_Content_Cache_Setup --
  354. ----------------------------------
  355. procedure Static_Content_Cache_Setup
  356. (No_Cache : in Boolean := False;
  357. No_Store : in Boolean := False;
  358. No_Transform : in Boolean := False;
  359. Max_Age : in AWS.Messages.Delta_Seconds := 86400;
  360. S_Max_Age : in AWS.Messages.Delta_Seconds := AWS.Messages.Unset;
  361. Public : in Boolean := False;
  362. Must_Revalidate : in Boolean := True;
  363. Proxy_Revalidate : in Boolean := False)
  364. is
  365. use AWS.Messages;
  366. begin
  367. GZip_And_Cache.Initialize;
  368. GZip_And_Cache.Set_Cache_Options
  369. (No_Cache => No_Cache,
  370. No_Store => No_Store,
  371. No_Transform => No_Transform,
  372. Max_Age => Max_Age,
  373. S_Max_Age => S_Max_Age,
  374. Public => Public,
  375. Must_Revalidate => Must_Revalidate,
  376. Proxy_Revalidate => Proxy_Revalidate);
  377. end Static_Content_Cache_Setup;
  378. end Yolk.Static_Content;