1 module hunt.http.codec.http.stream.AbstractHttp1OutputStream; 2 3 import hunt.http.HttpOutputStream; 4 import hunt.http.codec.http.encode.HttpGenerator; 5 6 import hunt.http.HttpBody; 7 import hunt.http.HttpMetaData; 8 import hunt.http.HttpRequest; 9 import hunt.http.HttpResponse; 10 11 import hunt.io.ByteBuffer; 12 import hunt.io.BufferUtils; 13 import hunt.net.Connection; 14 import hunt.Exceptions; 15 import hunt.logging; 16 17 import std.format; 18 19 20 /** 21 */ 22 abstract class AbstractHttp1OutputStream : HttpOutputStream { 23 24 this(HttpMetaData metaData, bool clientMode) { 25 super(metaData, clientMode); 26 } 27 28 override void commit() { 29 commit(cast(ByteBuffer)null); 30 } 31 32 protected void commit(ByteBuffer data) { 33 if (committed || closed) { 34 debug warning("connection closed already, or data committed already."); 35 return; 36 } 37 38 Connection tcpSession = getSession(); 39 if(!tcpSession.isConnected()) { 40 closed = true; 41 string msg = format("connection [id=%d] closed.", tcpSession.getId()); 42 debug warningf(msg); 43 return; 44 } 45 46 version(HUNT_HTTP_DEBUG) { 47 infof("committing data: %s", data.toString()); 48 } 49 50 HttpGenerator generator = getHttpGenerator(); 51 HttpGenerator.Result generatorResult; 52 ByteBuffer header = getHeaderByteBuffer(); 53 54 generatorResult = generate(metaData, header, null, data, false); 55 if (generatorResult == HttpGenerator.Result.FLUSH && 56 generator.getState() == HttpGenerator.State.COMMITTED) { 57 if (data !is null) { 58 // ByteBuffer[] headerAndData = [header, data]; 59 // tcpSession.encode(headerAndData); 60 tcpSession.encode(header); 61 tcpSession.encode(data); 62 } else { 63 tcpSession.encode(header); 64 } 65 committed = true; 66 } else { 67 generateHttpMessageExceptionally(generatorResult, generator.getState(), 68 HttpGenerator.Result.FLUSH, HttpGenerator.State.COMMITTED); 69 } 70 } 71 72 override void write(ByteBuffer data){ 73 if (closed) { 74 warningf("connection closed!"); 75 return; 76 } 77 78 if (!data.hasRemaining()) 79 return; 80 81 // The browser may close the connection forcely before receiving all data of favicon.ico. 82 Connection tcpSession = getSession(); 83 if(!tcpSession.isConnected()) { 84 closed = true; 85 string msg = format("connection [id=%d] closed.", tcpSession.getId()); 86 debug warningf(msg); 87 return; 88 } 89 90 HttpGenerator generator = getHttpGenerator(); 91 HttpGenerator.Result generatorResult; 92 93 if (!committed) { 94 commit(data); 95 } else { 96 if (generator.isChunking()) { 97 ByteBuffer chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); 98 99 generatorResult = generate(null, null, chunk, data, false); 100 if (generatorResult == HttpGenerator.Result.FLUSH && 101 generator.getState() == HttpGenerator.State.COMMITTED) { 102 // ByteBuffer[] chunkAndData = [chunk, data]; 103 // tcpSession.encode(chunkAndData); 104 tcpSession.encode(chunk); 105 tcpSession.encode(data); 106 } else { 107 generateHttpMessageExceptionally(generatorResult, generator.getState(), 108 HttpGenerator.Result.FLUSH, HttpGenerator.State.COMMITTED); 109 } 110 } else { 111 generatorResult = generate(null, null, null, data, false); 112 if (generatorResult == HttpGenerator.Result.FLUSH && 113 generator.getState() == HttpGenerator.State.COMMITTED) { 114 tcpSession.encode(data); 115 } else { 116 generateHttpMessageExceptionally(generatorResult, generator.getState(), 117 HttpGenerator.Result.FLUSH, HttpGenerator.State.COMMITTED); 118 } 119 } 120 } 121 } 122 123 override void close() { 124 if (closed) { 125 version(HUNT_HTTP_DEBUG) warning("The outstream has been closed."); 126 return; 127 } 128 129 try { 130 version(HUNT_HTTP_DEBUG) trace("The output stream for http1 is closing..."); 131 132 if(metaData.haveBody()) { 133 version(HUNT_HTTP_DEBUG) tracef("writting body..."); 134 HttpBody httpBody = metaData.getBody(); 135 httpBody.writeTo(this); 136 } 137 138 HttpGenerator generator = getHttpGenerator(); 139 Connection tcpSession = getSession(); 140 HttpGenerator.Result generatorResult; 141 142 if (!committed) { 143 ByteBuffer header = getHeaderByteBuffer(); 144 generatorResult = generate(metaData, header, null, null, true); 145 if (generatorResult == HttpGenerator.Result.FLUSH && 146 generator.getState() == HttpGenerator.State.COMPLETING) { 147 tcpSession.encode(header); 148 generateLastData(generator); 149 } else { 150 generateHttpMessageExceptionally(generatorResult, generator.getState(), 151 HttpGenerator.Result.FLUSH, HttpGenerator.State.COMPLETING); 152 } 153 committed = true; 154 } else { 155 if (generator.isChunking()) { 156 version (HUNT_HTTP_DEBUG) tracef("http1 output stream is generating chunk"); 157 generatorResult = generate(null, null, null, null, true); 158 if (generatorResult == HttpGenerator.Result.CONTINUE && 159 generator.getState() == HttpGenerator.State.COMPLETING) { 160 generatorResult = generate(null, null, null, null, true); 161 if (generatorResult == HttpGenerator.Result.NEED_CHUNK && 162 generator.getState() == HttpGenerator.State.COMPLETING) { 163 generateLastChunk(generator, tcpSession); 164 } else if (generatorResult == HttpGenerator.Result.NEED_CHUNK_TRAILER && 165 generator.getState() == HttpGenerator.State.COMPLETING) { 166 generateTrailer(generator, tcpSession); 167 } 168 } else { 169 generateHttpMessageExceptionally(generatorResult, generator.getState(), 170 HttpGenerator.Result.CONTINUE, HttpGenerator.State.COMPLETING); 171 } 172 } else { 173 generatorResult = generate(null, null, null, null, true); 174 if (generatorResult == HttpGenerator.Result.CONTINUE && 175 generator.getState() == HttpGenerator.State.COMPLETING) { 176 generateLastData(generator); 177 } else { 178 generateHttpMessageExceptionally(generatorResult, generator.getState(), 179 HttpGenerator.Result.CONTINUE, HttpGenerator.State.COMPLETING); 180 } 181 } 182 } 183 } finally { 184 closed = true; 185 version(HUNT_HTTP_DEBUG) infof("http1 output stream closed"); 186 } 187 } 188 189 private void generateLastChunk(HttpGenerator generator, Connection tcpSession) { 190 ByteBuffer chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); 191 HttpGenerator.Result generatorResult = generate(null, null, chunk, null, true); 192 if (generatorResult == HttpGenerator.Result.FLUSH && 193 generator.getState() == HttpGenerator.State.COMPLETING) { 194 tcpSession.encode(chunk); 195 generateLastData(generator); 196 } else { 197 generateHttpMessageExceptionally(generatorResult, generator.getState(), 198 HttpGenerator.Result.FLUSH, HttpGenerator.State.COMPLETING); 199 } 200 } 201 202 private void generateTrailer(HttpGenerator generator, Connection tcpSession) { 203 ByteBuffer trailer = getTrailerByteBuffer(); 204 HttpGenerator.Result generatorResult = generate(null, null, trailer, null, true); 205 if (generatorResult == HttpGenerator.Result.FLUSH && 206 generator.getState() == HttpGenerator.State.COMPLETING) { 207 tcpSession.encode(trailer); 208 generateLastData(generator); 209 } else { 210 generateHttpMessageExceptionally(generatorResult, generator.getState(), 211 HttpGenerator.Result.FLUSH, HttpGenerator.State.COMPLETING); 212 } 213 } 214 215 private void generateLastData(HttpGenerator generator) { 216 HttpGenerator.Result generatorResult = generate(null, null, null, null, true); 217 if (generator.getState() == HttpGenerator.State.END) { 218 if (generatorResult == HttpGenerator.Result.DONE) { 219 generateHttpMessageSuccessfully(); 220 } else if (generatorResult == HttpGenerator.Result.SHUTDOWN_OUT) { 221 getSession().close(); 222 } else { 223 generateHttpMessageExceptionally(generatorResult, generator.getState(), 224 HttpGenerator.Result.DONE, HttpGenerator.State.END); 225 } 226 } else { 227 generateHttpMessageExceptionally(generatorResult, generator.getState(), 228 HttpGenerator.Result.DONE, HttpGenerator.State.END); 229 } 230 } 231 232 protected HttpGenerator.Result generate(HttpMetaData metaData, 233 ByteBuffer header, ByteBuffer chunk, ByteBuffer content, bool last) { 234 HttpGenerator generator = getHttpGenerator(); 235 if (clientMode) { 236 return generator.generateRequest(cast(HttpRequest) metaData, header, chunk, content, last); 237 } else { 238 return generator.generateResponse(cast(HttpResponse) metaData, false, header, chunk, content, last); 239 } 240 } 241 242 abstract protected ByteBuffer getHeaderByteBuffer(); 243 244 abstract protected ByteBuffer getTrailerByteBuffer(); 245 246 abstract protected Connection getSession(); 247 248 abstract protected HttpGenerator getHttpGenerator(); 249 250 abstract protected void generateHttpMessageSuccessfully(); 251 252 abstract protected void generateHttpMessageExceptionally(HttpGenerator.Result actualResult, 253 HttpGenerator.State actualState, 254 HttpGenerator.Result expectedResult, 255 HttpGenerator.State expectedState); 256 257 }