Mercurial Hosting > luan
changeset 2008:bba3e529e346 default tip
chunked encoding
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 27 Aug 2025 01:14:17 -0600 |
parents | 408f7dd7e503 |
children | |
files | src/goodjava/io/IoUtils.java src/goodjava/webserver/ChunkedOutputStream.java src/goodjava/webserver/Connection.java src/goodjava/webserver/Response.java src/goodjava/webserver/ResponseOutputStream.java src/goodjava/webserver/examples/Chunked.java src/goodjava/webserver/examples/Example.java src/goodjava/webserver/handlers/ContentTypeHandler.java src/goodjava/webserver/handlers/FileHandler.java src/goodjava/webserver/handlers/LogHandler.java src/luan/modules/Boot.luan src/luan/modules/IoLuan.java src/luan/modules/http/Http.luan src/luan/modules/http/LuanHandler.java src/luan/modules/url/LuanUrl.java website/src/examples/chunked.html.luan |
diffstat | 16 files changed, 214 insertions(+), 25 deletions(-) [+] |
line wrap: on
line diff
--- a/src/goodjava/io/IoUtils.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/goodjava/io/IoUtils.java Wed Aug 27 01:14:17 2025 -0600 @@ -81,7 +81,7 @@ public static void copyAll(InputStream in,OutputStream out) throws IOException { - byte[] a = new byte[8192]; + byte[] a = new byte[32768]; int n; while( (n=in.read(a)) != -1 ) { out.write(a,0,n); @@ -92,7 +92,7 @@ public static void copyAll(Reader in,Writer out) throws IOException { - char[] a = new char[8192]; + char[] a = new char[32768]; int n; while( (n=in.read(a)) != -1 ) { out.write(a,0,n);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/ChunkedOutputStream.java Wed Aug 27 01:14:17 2025 -0600 @@ -0,0 +1,88 @@ +package goodjava.webserver; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.IOException; + + +public class ChunkedOutputStream extends ByteArrayOutputStream { + private static final byte[] end = "0\r\n\r\n".getBytes(); + private static final int OPEN = 1; + private static final int CLOSING = 2; + private static final int CLOSED = 3; + + private final Response response; + private int status = OPEN; + private int i = 0; + + public ChunkedOutputStream(Response response) { + if(response==null) throw new NullPointerException(); + this.response = response; + response.headers.put("Transfer-Encoding","chunked"); + response.body = new ChunkedInputStream(); + } + + @Override public synchronized void close() { + if( status == OPEN ) { + status = CLOSING; + notifyAll(); + } + } + + @Override public synchronized void write(int b) { + super.write(b); + notifyAll(); + } + + @Override public synchronized void write(byte b[], int off, int len) { + super.write(b,off,len); + notifyAll(); + } + + @Override public synchronized void reset() { + super.reset(); + i = 0; + } + + private class ChunkedInputStream extends InputStream { + + @Override public int read() { + throw new UnsupportedOperationException(); + } + + @Override public int read(byte[] b,int off,int len) throws IOException { + synchronized(ChunkedOutputStream.this) { + if( i == count ) { + if( status == CLOSED ) + return -1; + if( status == CLOSING ) { + System.arraycopy(end,0,b,off,end.length); + status = CLOSED; + return end.length; + } + try { + ChunkedOutputStream.this.wait(); + } catch(InterruptedException e) { + throw new RuntimeException(e); + } + return read(b,off,len); + } + int offOld = off; + int left = count - i; + int extra = Integer.toHexString(left).length() + 4; + int n = Math.min( len - extra, left ); + byte[] hex = Integer.toHexString(n).getBytes(); + System.arraycopy( hex, 0, b, off, hex.length ); + off += hex.length; + b[off++] = '\r'; b[off++] = '\n'; + System.arraycopy(buf,i,b,off,n); + off += n; + b[off++] = '\r'; b[off++] = '\n'; + i += n; + if( i == count ) + ChunkedOutputStream.this.reset(); + return off - offOld; + } + } + } +}
--- a/src/goodjava/webserver/Connection.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/goodjava/webserver/Connection.java Wed Aug 27 01:14:17 2025 -0600 @@ -129,12 +129,11 @@ response = Response.errorResponse(Status.BAD_REQUEST,msg); } response.headers.put("Connection","close"); - response.headers.put("Content-Length",Long.toString(response.body.length)); byte[] header = response.toHeaderString().getBytes(); OutputStream out = socket.getOutputStream(); out.write(header); - IoUtils.copyAll(response.body.content,out); + IoUtils.copyAll(response.body,out); out.close(); socket.close(); } catch(IOException e) {
--- a/src/goodjava/webserver/Response.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/goodjava/webserver/Response.java Wed Aug 27 01:14:17 2025 -0600 @@ -16,20 +16,15 @@ { headers.put("Server","goodjava"); } - private static final Body empty = new Body(0,new InputStream(){ - public int read() { return -1; } - }); - public volatile Body body = empty; - - public static class Body { - public final long length; - public final InputStream content; - - public Body(long length,InputStream content) { - this.length = length; - this.content = content; + private final InputStream empty = new InputStream() { + @Override public int read() { + return -1; } - } + @Override public void close() { + headers.put("Content-Length","0"); + } + }; + public volatile InputStream body = empty; public void addHeader(String name,String value) {
--- a/src/goodjava/webserver/ResponseOutputStream.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/goodjava/webserver/ResponseOutputStream.java Wed Aug 27 01:14:17 2025 -0600 @@ -5,7 +5,6 @@ import java.io.IOException; -// plenty of room for improvement public class ResponseOutputStream extends ByteArrayOutputStream { private final Response response; @@ -17,6 +16,7 @@ @Override public void close() throws IOException { super.close(); int size = size(); - response.body = new Response.Body( size, new ByteArrayInputStream(buf,0,size) ); + response.headers.put("Content-Length",Long.toString(size)); + response.body = new ByteArrayInputStream(buf,0,size); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/examples/Chunked.java Wed Aug 27 01:14:17 2025 -0600 @@ -0,0 +1,38 @@ +package goodjava.webserver.examples; + +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.util.Date; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.ChunkedOutputStream; +import goodjava.webserver.Server; + + +public class Chunked implements Handler { + + public Response handle(Request request) { + Response response = new Response(); + response.headers.put( "Content-Type", "text/html; charset=utf-8" ); + final Writer writer = new OutputStreamWriter( new ChunkedOutputStream(response) ); + new Thread(new Runnable(){public void run(){ + try { + String s = new Date().toString(); + for( int i=1; i<=10; i++ ) { + writer.write(s+" "+i+"<br>\n"); + writer.flush(); + Thread.sleep(1000); + } + writer.close(); + } catch(IOException e) { + throw new RuntimeException(e); + } catch(InterruptedException e) { + throw new RuntimeException(e); + } + }}).start(); + return response; + } + +}
--- a/src/goodjava/webserver/examples/Example.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/goodjava/webserver/examples/Example.java Wed Aug 27 01:14:17 2025 -0600 @@ -45,6 +45,7 @@ map.put( "/headers", new Headers() ); map.put( "/params", new Params() ); map.put( "/cookies", new Cookies() ); + map.put( "/chunked", new Chunked() ); Handler mapHandler = new MapHandler(map); FileHandler fileHandler = new FileHandler(); Handler dirHandler = new DirHandler(fileHandler);
--- a/src/goodjava/webserver/handlers/ContentTypeHandler.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/goodjava/webserver/handlers/ContentTypeHandler.java Wed Aug 27 01:14:17 2025 -0600 @@ -39,6 +39,7 @@ map.put( "ico", "image/x-icon" ); map.put( "mov", "video/quicktime" ); map.put( "mp3", "audio/mpeg" ); + map.put( "wav", "audio/wav" ); // add more as need }
--- a/src/goodjava/webserver/handlers/FileHandler.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/goodjava/webserver/handlers/FileHandler.java Wed Aug 27 01:14:17 2025 -0600 @@ -56,8 +56,8 @@ } catch(ParseException e) {} } response.headers.put("Last-Modified",lastModified); - - response.body = new Response.Body( file.length(), new FileInputStream(file) ); + response.headers.put("Content-Length",Long.toString(file.length())); + response.body = new FileInputStream(file); return response; } return null;
--- a/src/goodjava/webserver/handlers/LogHandler.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/goodjava/webserver/handlers/LogHandler.java Wed Aug 27 01:14:17 2025 -0600 @@ -67,7 +67,7 @@ return null; String ip = (String)request.headers.get("x-real-ip"); //String agent = (String)request.headers.get("user-agent"); - logger.info( ip + " \"" + request.method + " " + request.rawPath + "\" " + response.status.code + " " + response.body.length ); + logger.info( ip + " \"" + request.method + " " + request.rawPath + "\" " + response.status.code + " " + response.headers.get("Content-Length") ); return response; } }
--- a/src/luan/modules/Boot.luan Mon Jul 28 23:47:43 2025 -0600 +++ b/src/luan/modules/Boot.luan Wed Aug 27 01:14:17 2025 -0600 @@ -87,6 +87,7 @@ local this = {} this.java = writer this.write = writer.write + this.flush = writer.flush this.close = writer.close function this.write_from(reader)
--- a/src/luan/modules/IoLuan.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/luan/modules/IoLuan.java Wed Aug 27 01:14:17 2025 -0600 @@ -68,6 +68,10 @@ } } + public void flush() { + out.flush(); + } + public void close() { out.close(); } @@ -87,6 +91,10 @@ } } + public void flush() throws IOException { + out.flush(); + } + public void close() throws IOException { out.close(); }
--- a/src/luan/modules/http/Http.luan Mon Jul 28 23:47:43 2025 -0600 +++ b/src/luan/modules/http/Http.luan Wed Aug 27 01:14:17 2025 -0600 @@ -25,10 +25,12 @@ local format_time = Time.format or error() local parse_time = Time.parse or error() local Boot = require "luan:Boot.luan" +local Thread = require "luan:Thread.luan" local LuanJava = require "java:luan.Luan" local Request = require "java:goodjava.webserver.Request" local Response = require "java:goodjava.webserver.Response" local ResponseOutputStream = require "java:goodjava.webserver.ResponseOutputStream" +local ChunkedOutputStream = require "java:goodjava.webserver.ChunkedOutputStream" local Status = require "java:goodjava.webserver.Status" local ServerSentEvents = require "java:goodjava.webserver.ServerSentEvents" local OutputStreamWriter = require "java:java.io.OutputStreamWriter" @@ -182,6 +184,35 @@ return Boot.binary_writer(response.writer) end + function response.write_chunked_text(fn) + response.writer and error "writer already set" + response.writer = "done" + local writer = ChunkedOutputStream.new(response.java) + writer = OutputStreamWriter.new(writer) + writer = Boot.text_writer(writer) + Thread.run(function() + try + fn(writer) + finally + writer.close() + end + end) + end + + function response.write_chunked_binary(fn) + response.writer and error "writer already set" + response.writer = "done" + local writer = ChunkedOutputStream.new(response.java) + writer = Boot.binary_writer(writer) + Thread.run(function() + try + fn(writer) + finally + writer.close() + end + end) + end + return response end @@ -194,7 +225,7 @@ value = LuanJava.toJava(value) java.headers.put(name,value) end - response.writer and response.writer.close() + response.writer and response.writer~="done" and response.writer.close() return java end
--- a/src/luan/modules/http/LuanHandler.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/luan/modules/http/LuanHandler.java Wed Aug 27 01:14:17 2025 -0600 @@ -19,7 +19,6 @@ import goodjava.webserver.Status; import goodjava.webserver.Server; import goodjava.webserver.Handler; -import goodjava.webserver.ResponseOutputStream; import luan.Luan; import luan.LuanTable; import luan.LuanFunction;
--- a/src/luan/modules/url/LuanUrl.java Mon Jul 28 23:47:43 2025 -0600 +++ b/src/luan/modules/url/LuanUrl.java Wed Aug 27 01:14:17 2025 -0600 @@ -27,9 +27,12 @@ import luan.modules.IoLuan; import luan.modules.StringLuan; import luan.modules.Utils; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; public final class LuanUrl extends IoLuan.LuanIn { + private static final Logger logger = LoggerFactory.getLogger(LuanUrl.class); private static enum Method { GET, POST, DELETE, PUT } @@ -280,7 +283,11 @@ throws IOException, LuanException, AuthException { try { - return httpCon.getInputStream(); +// return httpCon.getInputStream(); + InputStream in = httpCon.getInputStream(); +// System.err.println("Content-Length = "+httpCon.getHeaderField("Content-Length")); +// System.err.println("Transfer-Encoding = "+httpCon.getHeaderField("Transfer-Encoding")); + return in; // } catch(FileNotFoundException e) { // throw e; } catch(IOException e) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/website/src/examples/chunked.html.luan Wed Aug 27 01:14:17 2025 -0600 @@ -0,0 +1,21 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local range = Luan.range or error() +local Thread = require "luan:Thread.luan" +local sleep = Thread.sleep or error() +local Io = require "luan:Io.luan" +local Http = require "luan:http/Http.luan" + + +return function() + Http.response.write_chunked_text(function(writer) + Io.stdout = writer + for i in range(1,10) do +%> +line <%=i%><br> +<% + writer.flush() + sleep(1000) + end + end) +end