FileStaticResource.vala 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. using Inversion;
  2. using Astralis;
  3. using Invercargill;
  4. using Invercargill.DataStructures;
  5. namespace Spry {
  6. public class FileStaticResource : Object, StaticResource {
  7. private string _name;
  8. private string _content_type;
  9. private string _e_tag;
  10. private ImmutableBuffer<EncodingHeader> _encodings;
  11. private Dictionary<string, EncodingHeader> _encoding_mapping;
  12. public string file_path { get; private set; }
  13. public uint64 header_length { get; private set; }
  14. public FileStaticResource(string path) throws Error {
  15. this.file_path = path;
  16. var stream = new DataInputStream(File.new_for_path(path).read());
  17. read_header(stream);
  18. stream.close();
  19. }
  20. public string get_best_encoding (Set<string> supported) {
  21. return _encodings.first_or_default(e => supported.has(e.type))?.type ?? "identity";
  22. }
  23. public HttpResult to_result(string encoding, StatusCode status = StatusCode.OK) throws Error {
  24. return new StreamingResult(this, _encoding_mapping[encoding], status);
  25. }
  26. public string name { get { return _name; } }
  27. public string content_type { get { return _content_type; } }
  28. public string get_etag_for (string encoding) {
  29. return @"\"$encoding-$_e_tag\"";
  30. }
  31. private void read_header(DataInputStream stream) throws Error {
  32. var magic = new uint8[8];
  33. stream.read(magic);
  34. if(!Wrap.array(magic).equals(Iterate.these<uint8>('s', 'p', 'r', 'y', '-', 's', 'r', 0))) {
  35. throw new IOError.INVALID_DATA("Incorrect magic number on Spry Static Resource file.");
  36. }
  37. _name = read_string_field(stream);
  38. _content_type = read_string_field(stream);
  39. var hash_len = (size_t)64;
  40. var hash_composition = new ByteComposition();
  41. while(hash_composition.read_in (stream.read_bytes(hash_len), ref hash_len));
  42. _e_tag = hash_composition.to_base64();
  43. var encodings = new Buffer<EncodingHeader>(stream.read_byte());
  44. for(var i = 0; i < encodings.length; i++) {
  45. encodings[i] = new EncodingHeader(stream);
  46. }
  47. _encodings = encodings
  48. .order_by<uint64?>(e => e.size)
  49. .to_immutable_buffer();
  50. _encoding_mapping = encodings
  51. .to_dictionary<string>(e => e.type);
  52. header_length = stream.tell();
  53. if(!_encoding_mapping.has("identity")) {
  54. throw new IOError.INVALID_DATA("Spry Static Resource file does not contain required \'identity\' encoding.");
  55. }
  56. }
  57. private static string read_string_field(DataInputStream stream) throws Error {
  58. var len = (size_t)stream.read_byte();
  59. var composition = new ByteComposition();
  60. while(composition.read_in (stream.read_bytes(len), ref len));
  61. return composition.to_raw_string();
  62. }
  63. private class EncodingHeader {
  64. public string type;
  65. public uint64 offset;
  66. public uint64 size;
  67. public EncodingHeader(DataInputStream stream) throws Error {
  68. type = read_string_field(stream);
  69. offset = stream.read_uint64();
  70. size = stream.read_uint64();
  71. }
  72. }
  73. private class StreamingResult : HttpResult {
  74. private FileStaticResource resource;
  75. private EncodingHeader encoding;
  76. public StreamingResult(FileStaticResource resource, EncodingHeader encoding, StatusCode status) {
  77. base(status);
  78. this.resource = resource;
  79. this.encoding = encoding;
  80. set_header("Content-Type", resource.content_type);
  81. set_header("Content-Encoding", encoding.type);
  82. set_header("Content-Length", encoding.size.to_string());
  83. set_header("ETag", resource.get_etag_for(encoding.type));
  84. }
  85. public async override void send_body(Astralis.AsyncOutput output) throws Error {
  86. var stream = yield File.new_for_path(resource.file_path).read_async();
  87. var data_len = encoding.size;
  88. stream.seek((int64)resource.header_length, SeekType.SET);
  89. while(data_len > 0) {
  90. var to_read = (size_t)uint64.min(data_len, 1024 * 1024 * 4);
  91. var bytes = yield stream.read_bytes_async(to_read);
  92. data_len -= bytes.length;
  93. yield output.write(Wrap.bytes(bytes));
  94. }
  95. }
  96. }
  97. }
  98. }