1 module lighttp.server.resource; 2 3 import std.array : Appender; 4 import std.base64 : Base64URLNoPadding; 5 import std.digest.crc : crc32Of; 6 import std.string : indexOf, strip; 7 import std.zlib : HeaderFormat, Compress; 8 9 import lighttp.util : StatusCodes, Http; 10 11 class Resource { 12 13 private immutable string mime; 14 15 public immutable size_t thresold; 16 17 public const(void)[] uncompressed; 18 public const(void)[] compressed = null; 19 20 public this(string mime, size_t thresold=512) { 21 this.mime = mime; 22 this.thresold = thresold; 23 } 24 25 public this(string mime, in void[] data) { 26 this(mime); 27 this.data = data; 28 } 29 30 public @property const(void)[] data(in void[] data) { 31 this.uncompressed = data; 32 if(data.length >= this.thresold) this.compress(); 33 else this.compressed = null; 34 return this.uncompressed; 35 } 36 37 private void compress() { 38 Compress compress = new Compress(6, HeaderFormat.gzip); 39 auto data = compress.compress(this.uncompressed); 40 data ~= compress.flush(); 41 this.compressed = data; 42 } 43 44 public void apply(Http req, Http res) { 45 if(this.compressed !is null && req.headers.get("accept-encoding", "").indexOf("gzip") != -1) { 46 res.headers["Content-Encoding"] = "gzip"; 47 res.body_ = cast(string)this.compressed; 48 } else { 49 res.body_ = cast(string)this.uncompressed; 50 } 51 res.contentType = this.mime; 52 } 53 54 } 55 56 class CachedResource : Resource { 57 58 private string etag; 59 60 public this(string mime, size_t thresold=512) { 61 super(mime, thresold); 62 } 63 64 public this(string mime, in void[] data) { 65 super(mime, data); 66 } 67 68 public override @property const(void)[] data(in void[] data) { 69 this.etag = Base64URLNoPadding.encode(crc32Of(data)); 70 return super.data(data); 71 } 72 73 public override void apply(Http req, Http res) { 74 if(req.headers.get("if-none-match", "") == this.etag) { 75 res.status = StatusCodes.notModified; 76 } else { 77 super.apply(req, res); 78 res.headers["Cache-Control"] = "public, max-age=31536000"; 79 res.headers["ETag"] = this.etag; 80 } 81 } 82 83 } 84 85 class TemplatedResource : Resource { 86 87 private abstract class Data { 88 89 abstract string translate(string[string]); 90 91 } 92 93 private class StringData : Data { 94 95 string data; 96 97 this(string data) { 98 this.data = data; 99 } 100 101 override string translate(string[string] dictionary) { 102 return data; 103 } 104 105 } 106 107 private class TranslateData : Data { 108 109 string key; 110 111 this(string key) { 112 this.key = key; 113 } 114 115 override string translate(string[string] dictionary) { 116 auto ptr = key in dictionary; 117 return ptr ? *ptr : ""; 118 } 119 120 } 121 122 private Data[] tdata; 123 124 public this(string mime, size_t thresold=512) { 125 super(mime, thresold); 126 } 127 128 public this(string mime, in void[] _data) { 129 this(mime); 130 string data = cast(string)_data; 131 while(data.length) { 132 immutable start = data.indexOf("{{"); 133 if(start != -1) { 134 this.tdata ~= new StringData(data[0..start]); 135 data = data[start+2..$]; 136 immutable end = data.indexOf("}}"); 137 if(end != -1) { 138 this.tdata ~= new TranslateData(data[0..end].strip); 139 data = data[end+2..$]; 140 } 141 } else { 142 this.tdata ~= new StringData(data); 143 break; 144 } 145 } 146 } 147 148 public Resource apply(string[string] dictionary) { 149 Appender!string appender; 150 foreach(data ; this.tdata) { 151 appender.put(data.translate(dictionary)); 152 } 153 this.data = appender.data; 154 return this; 155 } 156 157 }