1 module hunt.http.HttpBody; 2 3 import hunt.http.HttpOutputStream; 4 import hunt.io.HeapByteBuffer; 5 import hunt.io.ByteBuffer; 6 import hunt.io.BufferUtils; 7 8 import hunt.Exceptions; 9 import hunt.logging; 10 import hunt.util.MimeType; 11 import hunt.util.MimeTypeUtils; 12 13 import std.array; 14 import std.conv; 15 import std.file; 16 import std.path; 17 import std.range; 18 import std.stdio; 19 import std.traits; 20 21 /** 22 * A body content that can be sent or received with an HTTP message. 23 * 24 * See_Also: 25 * org.apache.hc.core5.http.HttpEntity 26 */ 27 abstract class HttpBody { 28 29 string contentType(); 30 31 /** 32 * Returns the number of bytes that will be written to {@code sink} in a call to {@link #writeTo}, 33 * or -1 if that count is unknown. 34 */ 35 long contentLength() { 36 return -1; 37 } 38 39 void append(ubyte[] data) { 40 implementationMissing(false); 41 } 42 43 void append(ByteBuffer buffer) { 44 ubyte[] content = cast(ubyte[])buffer.peekRemaining(); 45 append(content); 46 } 47 48 /** Writes the content of this request to {@code sink}. */ 49 void writeTo(HttpOutputStream sink); 50 51 // getContent() 52 string asString() { 53 implementationMissing(false); 54 return ""; 55 } 56 57 const(ubyte)[] getRaw() { 58 implementationMissing(false); 59 return null; 60 } 61 62 override string toString() { 63 return asString(); 64 } 65 66 static HttpBody create(T)(T content) { 67 static if(isSomeString!T) { 68 return create(MimeType.TEXT_HTML_VALUE, content); 69 } else static if(isBasicType!T) { 70 return create(MimeType.TEXT_HTML_VALUE, content.to!string); 71 } else { 72 // using defaults 73 74 // TODO: Tasks pending completion -@zhangxueping at 2020-01-08T10:21:19+08:00 75 // return struct and class as json string 76 version(HUNT_HTTP_DEBUG) warningf("Using default conversion for %s", T.stringof); 77 return create(MimeType.TEXT_HTML_VALUE, content.to!string); 78 } 79 } 80 81 // static HttpBody create(T)(string contentType, T content) if(!isSomeString!T) { 82 // return create(contentType, content.to!string); 83 // } 84 85 static HttpBody create(string contentType, long contentLength) { 86 HttpBody impl = new HttpBodyImpl(contentType, contentLength); 87 return impl; 88 } 89 90 /** 91 * Returns a new request body that transmits {@code content}. If {@code contentType} is non-null 92 * and lacks a charset, this will use UTF-8. 93 */ 94 static HttpBody create(string contentType, string content) { 95 // Charset charset = UTF_8; 96 if (contentType !is null) { 97 // charset = contentType.charset(); 98 string charset = new MimeType(contentType).getCharset(); 99 if (charset.empty()) { 100 // charset = UTF_8; 101 contentType = contentType ~ ";charset=utf-8"; 102 } 103 } 104 ubyte[] bytes = cast(ubyte[])content; // content.getBytes(charset); 105 return create(contentType, bytes); 106 } 107 108 static HttpBody create(string contentType, ByteBuffer buffer) { 109 ubyte[] content = cast(ubyte[])buffer.peekRemaining(); 110 return create(contentType, content); 111 } 112 113 /** Returns a new request body that transmits {@code content}. */ 114 static HttpBody create(string type, const(ubyte)[] content) { 115 // return create(contentType, content, 0, cast(int)content.length); 116 HttpBody impl = new HttpBodyImpl(type, content); 117 return impl; 118 } 119 120 /** Returns a new request body that transmits the content of {@code file}. */ 121 static HttpBody createFromFile(string type, string fileName) { 122 if (fileName.empty()) throw new NullPointerException("fileName is null"); 123 124 string rootPath = dirName(thisExePath); 125 string abslutePath = buildPath(rootPath, fileName); 126 version(HUNT_HTTP_DEBUG) info("generate request body from file: ", abslutePath); 127 if(!abslutePath.exists) throw new FileNotFoundException(fileName); 128 129 DirEntry entry = DirEntry(abslutePath); 130 if(entry.isDir()) throw new FileException("Can't handle a direcotry: ", abslutePath); 131 132 long total = cast(long)entry.size(); 133 if(total > 10*1024*1024) { 134 // throw new FileException("The file is too big to upload (< 10MB)."); 135 debug warning("uploading a big file: %d MB", total/1024/1024); 136 } 137 138 // dfmt off 139 return new class HttpBody { 140 override string contentType() { 141 return type; 142 } 143 144 override long contentLength() { 145 return total; 146 } 147 148 149 override string asString() { 150 // FIXME: Needing refactor or cleanup -@zhangxueping at 2020-01-07T18:58:40+08:00 151 // 152 return fileName; 153 } 154 155 override void writeTo(HttpOutputStream sink) { 156 version(HUNT_HTTP_DEBUG) infof("loading data from: %s, size: %d", abslutePath, total); 157 enum MaxBufferSize = 16*1024; 158 ubyte[] buffer; 159 160 File f = File(abslutePath, "r"); 161 scope(exit) f.close(); 162 163 size_t remaining = cast(size_t)total; 164 while(remaining > 0 && !f.eof()) { 165 166 if(remaining > MaxBufferSize) 167 buffer = new ubyte[MaxBufferSize]; 168 else 169 buffer = new ubyte[remaining]; 170 ubyte[] data = f.rawRead(buffer); 171 172 if(data.length > 0) { 173 sink.write(cast(byte[])data); 174 remaining -= cast(int)data.length; 175 } 176 version(HUNT_HTTP_DEBUG_MORE) { 177 tracef("read: %s, remaining: %d, eof: %s", 178 data.length, remaining, f.eof()); 179 } 180 } 181 } 182 }; 183 // dfmt on 184 } 185 } 186 187 188 class HttpBodyImpl : HttpBody { 189 190 private Appender!(const(ubyte)[]) _buffer; 191 private string _type; 192 private long _contentLength; 193 194 this(string type, long contentLength) { 195 _type = type; 196 _contentLength = contentLength; 197 } 198 199 this(string type, const(ubyte)[] content) { 200 _type = type; 201 _contentLength = cast(long)content.length; 202 append(content); 203 } 204 205 override string contentType() { 206 return _type; 207 } 208 209 override long contentLength() { 210 return _contentLength; 211 } 212 213 override void append(const(ubyte)[] data) { 214 _buffer.put(data); 215 } 216 217 override string asString() { 218 string str = cast(string)_buffer.data(); 219 version(HUNT_DEBUG) { 220 if(_contentLength != -1 && str.length != _contentLength) { 221 warningf("Mismatched content length, required: %d, actual: %d", _contentLength, str.length); 222 } 223 } 224 return str; 225 } 226 227 override const(ubyte)[] getRaw() { 228 const(ubyte)[] data = _buffer.data; 229 version(HUNT_DEBUG) { 230 if(_contentLength != -1 && data.length != _contentLength) { 231 warningf("Mismatched content length, required: %d, actual: %d", _contentLength, data.length); 232 } 233 } 234 return data; 235 } 236 237 override void writeTo(HttpOutputStream sink) { 238 auto d = cast(byte[])_buffer.data; 239 if(d.length > 0) { 240 sink.write(d, 0, cast(int)d.length); 241 } 242 } 243 }