├── .gitignore ├── LICENSE.txt ├── README.md ├── pom.xml ├── resources └── _bayou │ └── __resource │ ├── _readme.txt │ ├── mime.types.txt │ └── public.suffix.txt └── src ├── _bayou ├── _HttpProxy.java ├── _HttpTunnel.java ├── _StaticServer.java ├── _async │ ├── _AsyncDoWhile.java │ ├── _Asyncs.java │ ├── _Fiber_Stack_Trace_.java │ ├── _WithPreferredFiberDefaultExec.java │ └── _WithThreadLocalFiber.java ├── _bytes │ ├── _ByteSourceSkipper.java │ ├── _DelimitedByteSource.java │ ├── _EmptyByteSource.java │ └── _ErrorByteSource.java ├── _http │ ├── _HttpDate.java │ ├── _HttpHostPort.java │ ├── _HttpUtil.java │ ├── _Rfc6265.java │ └── _SimpleRequestEntity.java ├── _log │ ├── _Logger.java │ ├── _LoggerS.java │ └── _SimpleLogger.java ├── _str │ ├── _ByteArr.java │ ├── _ChArr.java │ ├── _ChArrCi.java │ ├── _CharArray2Seq.java │ ├── _CharDef.java │ ├── _CharSeqSaver.java │ ├── _CharSubSeq.java │ ├── _CharsetDecoder.java │ ├── _HexUtil.java │ ├── _HoehrmannUtf8Decoder.java │ ├── _StrCi.java │ ├── _StrUtil.java │ └── _StringSaver.java ├── _tmp │ ├── _Array2ReadOnlyList.java │ ├── _ByteBufferPool.java │ ├── _ByteBufferUtil.java │ ├── _ControlException.java │ ├── _CryptoUtil.java │ ├── _Dns.java │ ├── _Exec.java │ ├── _FileMonitor.java │ ├── _Ip.java │ ├── _JobTimeout.java │ ├── _KnownHeaders.java │ ├── _NamedThreadFactory.java │ ├── _PrefixedMsgOut.java │ ├── _PublicSuffix.java │ ├── _StreamIter.java │ ├── _Tcp.java │ ├── _TcpConn2Chann.java │ ├── _TrafficDumpWrapper.java │ └── _Util.java └── package-info.java └── bayou ├── async ├── Async.java ├── AsyncBundle.java ├── AsyncIterator.java ├── AsyncThen.java ├── AsyncTimeout.java ├── Asyncs.java ├── AutoAsync.java ├── CallbackList.java ├── Fiber.java ├── FiberDefaultExecutors.java ├── FiberLocal.java ├── FlatMapIterator.java ├── ForEachNoAsync.java ├── Promise.java └── package-info.java ├── bytes ├── BytePipe.java ├── ByteSink.java ├── ByteSource.java ├── ByteSource2InputStream.java ├── ByteSourceCache.java ├── InputStream2ByteSource.java ├── PushbackByteSource.java ├── RangedByteSource.java ├── SimpleByteSource.java ├── ThreadSafeByteSource.java ├── ThrottledByteSource.java └── package-info.java ├── file ├── FileByteSink.java ├── FileByteSource.java ├── FileHttpEntity.java ├── StaticFileConf.java ├── StaticHandler.java ├── UriPath.java └── package-info.java ├── form ├── CsrfException.java ├── CsrfToken.java ├── DoGenUrlEncoded.java ├── DoParseMultipart.java ├── DoParseUrlEncoded.java ├── DoSaveToTmpFile.java ├── FormData.java ├── FormDataFile.java ├── FormDataHttpEntity.java ├── FormParser.java └── package-info.java ├── gzip ├── GunzipByteSource.java ├── GzipByteSource.java ├── GzipHeaderParser.java ├── GzipHttpEntity.java └── package-info.java ├── html ├── Html4.java ├── Html4Doc.java ├── Html5.java ├── Html5Doc.java ├── HtmlAttributeMap.java ├── HtmlBuilder.java ├── HtmlChildList.java ├── HtmlComment.java ├── HtmlDoc.java ├── HtmlElement.java ├── HtmlElementType.java ├── HtmlHelper.java ├── HtmlNewLine.java ├── HtmlParent.java ├── HtmlParentElement.java ├── HtmlPiece.java ├── HtmlRaw.java ├── HtmlText.java └── package-info.java ├── http ├── AuthHandler.java ├── CachedHttpEntity.java ├── ConnectTunnel.java ├── Cookie.java ├── CookieHandler.java ├── CookieJar.java ├── CookieStorage.java ├── HotHttpHandler.java ├── HttpAccess.java ├── HttpAccessLoggerWrapper.java ├── HttpClient.java ├── HttpClientConf.java ├── HttpClientConnMan.java ├── HttpClientConnection.java ├── HttpClientInbound.java ├── HttpClientOutbound.java ├── HttpEntity.java ├── HttpEntityMod.java ├── HttpEntityWrapper.java ├── HttpHandler.java ├── HttpHelper.java ├── HttpProxy.java ├── HttpRequest.java ├── HttpRequestImpl.java ├── HttpResponse.java ├── HttpResponseException.java ├── HttpResponseImpl.java ├── HttpServer.java ├── HttpServerConf.java ├── HttpStatus.java ├── HttpUpgrader.java ├── ImplChunkedSource.java ├── ImplConn.java ├── ImplConnReq.java ├── ImplConnResp.java ├── ImplHttpChunkedBody.java ├── ImplHttpEntity.java ├── ImplHttpRequest.java ├── ImplReqHeadParser.java ├── ImplRespHeadParser.java ├── ImplRespMod.java ├── ImplTunneller.java ├── InMemoryCookieStorage.java ├── RedirectHandler.java ├── RequestTarget.java ├── SimpleHttpEntity.java ├── SimpleHttpResponse.java ├── ThrottledHttpEntity.java └── package-info.java ├── mime ├── ContentType.java ├── FileSuffixToContentType.java ├── HeaderMap.java ├── Headers.java ├── MultipartByteSource.java ├── MultipartParser.java ├── MultipartPart.java ├── Rfc822HeadParser.java ├── TokenParams.java └── package-info.java ├── reload ├── HotCompiler.java ├── HotReloader.java ├── JavacCompiler.java ├── VisitClasses.java ├── VoidCompiler.java └── package-info.java ├── ssl ├── SslChannel2Connection.java ├── SslConf.java ├── SslConnection.java ├── SslConnectionImpl.java ├── SslDetector.java ├── SslHandshaker.java └── package-info.java ├── tcp ├── AsyncConnection.java ├── AsyncConnectionImpl.java ├── ChannImpl.java ├── PlainTcpConnection.java ├── SelectorThread.java ├── Socks5Tunnel.java ├── TcpAddress.java ├── TcpChannel.java ├── TcpChannel2Connection.java ├── TcpClient.java ├── TcpConnection.java ├── TcpServer.java ├── TcpTunnel.java └── package-info.java ├── text ├── TextByteSource.java ├── TextDoc.java ├── TextHttpEntity.java └── package-info.java ├── util ├── End.java ├── OverLimitException.java ├── Result.java ├── Result_Failure.java ├── Result_Success.java ├── Tuple2.java ├── UserPass.java ├── Var.java ├── Var0.java ├── Var1.java ├── Var2.java ├── VarAbs.java ├── function │ ├── BiFunctionX.java │ ├── Callable_Void.java │ ├── ConsumerX.java │ ├── FunctionX.java │ ├── RunnableX.java │ └── package-info.java └── package-info.java └── websocket ├── ByteSource2WebSocketMessage.java ├── HotWebSocketHandler.java ├── ThroughputMeter.java ├── WebSocketChannel.java ├── WebSocketChannelImpl.java ├── WebSocketClose.java ├── WebSocketException.java ├── WebSocketHandler.java ├── WebSocketInbound.java ├── WebSocketMessage.java ├── WebSocketOutbound.java ├── WebSocketRequest.java ├── WebSocketResponse.java ├── WebSocketServer.java ├── WebSocketServerConf.java ├── WsOp.java └── package-info.java /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | License 2 | 3 | Unless otherwise specified, all files included in this distribution are under 4 | 5 | Apache License 2.0 6 | http://www.apache.org/licenses/LICENSE-2.0.html 7 | 8 | Exceptions: 9 | 10 | _HoehrmannUtf8Decoder.java 11 | 12 | This is derived from Bjoern Hoehrmann's UTF-8 decoder, see license detail at 13 | http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bayou.io 2 | 3 | Async http server & client for Java 4 | 5 | For more information, see 6 | 7 | ## HttpServer 8 | 9 | HttpHandler handler = request -> HttpResponse.text(200, "Hello World"); 10 | 11 | HttpServer server = new HttpServer(handler); 12 | server.conf().trafficDump(System.out::print); 13 | server.start(); 14 | 15 | 16 | ## HttpClient 17 | 18 | HttpClient client = new HttpClient(); 19 | 20 | Async asyncRes = client.doGet( "https://example.com" ); 21 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | 6 | 7 | jitpack.io 8 | https://jitpack.io 9 | 10 | 11 | 12 | com.github.zhong-j-yu 13 | bayou 14 | 1.0.0 15 | 16 | 17 | 18 | src 19 | 20 | 21 | resources 22 | 23 | 24 | 25 | 26 | 27 | org.apache.maven.plugins 28 | maven-compiler-plugin 29 | 3.3 30 | 31 | 1.8 32 | 1.8 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /resources/_bayou/__resource/_readme.txt: -------------------------------------------------------------------------------- 1 | This directory contains resource files. 2 | If a file is outdated, you may download it from the source and replace the file. 3 | Or you may modify the file for your own reasons. 4 | 5 | Files: 6 | 7 | 8 | mime.types.txt 9 | source: 10 | http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=co 11 | 12 | public.suffix.txt 13 | source: 14 | https://publicsuffix.org/list/effective_tld_names.dat 15 | -------------------------------------------------------------------------------- /src/_bayou/_HttpProxy.java: -------------------------------------------------------------------------------- 1 | package _bayou; 2 | 3 | import bayou.http.*; 4 | 5 | import java.time.Duration; 6 | 7 | // simple demo of HTTP+HTTPS proxy at port 9090 8 | public class _HttpProxy 9 | { 10 | public static void main(String[] args) throws Exception 11 | { 12 | int port = 9090; 13 | 14 | HttpClient downstream = new HttpClient(); 15 | HttpHandler handler = request-> 16 | { 17 | if(request.method().equals("CONNECT")) 18 | { 19 | System.err.println("## TUNNEL ## "+request.host()); 20 | return HttpResponse.text(200, "tunneling request granted"); 21 | } 22 | else 23 | { 24 | System.err.println("## FORWARD ## "+request.absoluteUri()); 25 | return downstream.send0(request, null); 26 | } 27 | }; 28 | 29 | HttpServer proxy = new HttpServer(handler); 30 | proxy.conf() 31 | .port(port) 32 | .setProxyDefaults() 33 | .trafficDump(System.out::print) 34 | ; 35 | proxy.start(); 36 | 37 | System.out.printf("Bayou Demo HTTP+HTTPS Proxy, port = %s %n", port); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/_bayou/_HttpTunnel.java: -------------------------------------------------------------------------------- 1 | package _bayou; 2 | 3 | import bayou.http.HttpHandler; 4 | import bayou.http.HttpRequest; 5 | import bayou.http.HttpResponse; 6 | import bayou.http.HttpServer; 7 | 8 | // a basic http tunnel. for demo and testing. 9 | public class _HttpTunnel 10 | { 11 | public static void main(String[] args) throws Exception 12 | { 13 | //args = new String[]{"9090"}; 14 | 15 | if(args.length!=1) 16 | { 17 | System.out.printf("%nUsage: java %s %n", _HttpTunnel.class.getName()); 18 | return; 19 | } 20 | 21 | int port = Integer.parseInt(args[0]); 22 | 23 | HttpHandler handler = request-> 24 | { 25 | if(request.method().equals("CONNECT")) // a tunneling request 26 | { 27 | if(isAllowed(request)) 28 | return HttpResponse.text(200, "tunneling request granted"); 29 | else 30 | return HttpResponse.text(403, "tunneling request denied"); 31 | } 32 | 33 | return HttpResponse.text(404, "Not Found"); 34 | }; 35 | 36 | HttpServer server = new HttpServer(handler); 37 | server.conf().port(port); 38 | server.conf().supportedMethods("CONNECT", "GET", "HEAD"); 39 | server.conf().trafficDump(System.out::print); 40 | server.start(); 41 | System.out.printf("Bayou Demo HTTP Tunnel, port = %s %n", port); 42 | } 43 | 44 | static boolean isAllowed(HttpRequest request) 45 | { 46 | // todo: check the client, e.g. only allow clients from certain ips 47 | // todo: check the target server, e.g. only allow certain target servers 48 | return true; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/_bayou/_StaticServer.java: -------------------------------------------------------------------------------- 1 | package _bayou; 2 | 3 | import bayou.file.StaticHandler; 4 | import bayou.http.HttpServer; 5 | 6 | // a basic static server. for demo and testing. 7 | public class _StaticServer 8 | { 9 | public static void main(String[] args) throws Exception 10 | { 11 | if(args.length!=1) 12 | { 13 | System.out.printf("%nUsage: java %s %n", _StaticServer.class.getName()); 14 | return; 15 | } 16 | 17 | String rootDir = args[0]; 18 | StaticHandler handler = new StaticHandler("/", rootDir); // throws 19 | System.out.printf("%nBayou Demo Static Server, rootDir = %s %n", rootDir); 20 | 21 | HttpServer server = new HttpServer(handler); 22 | server.start(); // localhost:8080 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/_bayou/_async/_AsyncDoWhile.java: -------------------------------------------------------------------------------- 1 | package _bayou._async; 2 | 3 | import bayou.async.Async; 4 | import bayou.async.Promise; 5 | import bayou.util.Result; 6 | 7 | import java.util.function.Consumer; 8 | 9 | // async version of 10 | // do{ action } while( condition ) 11 | public abstract class _AsyncDoWhile implements Consumer> 12 | { 13 | final Promise promise = new Promise<>(); 14 | 15 | protected abstract Async action(); 16 | 17 | // return null to continue the loop 18 | // otherwise, loop ends, yielding the returned value. 19 | protected abstract Result condition(Result result); 20 | 21 | public Async run() 22 | { 23 | loop(); 24 | return promise; 25 | } 26 | 27 | void loop() 28 | { 29 | // if the promise receives a cancel request, it'll forward to `action`. 30 | // however `action` may miss or ignore the cancel request. 31 | // so the loop itself also checks and honors the cancel request. 32 | Exception cancelReq = promise.pollCancel(); 33 | if(cancelReq!=null) 34 | { 35 | promise.fail(cancelReq); 36 | return; 37 | } 38 | 39 | Async action = action(); 40 | promise.onCancel(action::cancel); 41 | action.onCompletion(this); 42 | 43 | // if action is completed, we could optimize by a local loop, avoiding heavy onCompletion mechanism. 44 | // then the loop may occupy the current thread; run() may not return before loop is finished, 45 | // giving no chance for caller to cancel it. 46 | // so we don't do the optimization. in a typical async app, action is usually not so cheap, 47 | // so the optimization wouldn't do much good anyway. 48 | } 49 | 50 | // action completes 51 | @Override 52 | final 53 | public void accept(Result result) 54 | { 55 | Result end = condition(result); 56 | if(end!=null) 57 | promise.complete(end); 58 | else 59 | loop(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/_bayou/_async/_Fiber_Stack_Trace_.java: -------------------------------------------------------------------------------- 1 | package _bayou._async; 2 | 3 | import bayou.async.Fiber; 4 | 5 | import java.util.Arrays; 6 | import java.util.HashSet; 7 | 8 | // not used as a real exception 9 | public class _Fiber_Stack_Trace_ extends Exception 10 | { 11 | public _Fiber_Stack_Trace_() 12 | { 13 | 14 | } 15 | 16 | @Override 17 | public Throwable fillInStackTrace() 18 | { 19 | return this; 20 | } 21 | 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | 24 | public static boolean isAsyncScaffold(StackTraceElement frame) 25 | { 26 | String clazz = frame.getClassName(); 27 | if(specialMethods.contains(clazz+"#"+frame.getMethodName())) 28 | return false; 29 | if(clazz.startsWith("bayou.util.Result")) 30 | return true; 31 | if(clazz.startsWith("_bayou._async.")) 32 | return true; 33 | if(clazz.startsWith("bayou.async.")) 34 | return true; 35 | 36 | return false; 37 | } 38 | 39 | static final HashSet specialMethods = new HashSet<>(Arrays.asList( 40 | "bayou.async.Async#execute", 41 | // terminal ops in AsyncIterator? forEach() etc 42 | "bayou.async.Fiber#sleep", "bayou.async.Fiber#join", 43 | "bayou.async.AsyncBundle#anyOf", "bayou.async.AsyncBundle#allOf", "bayou.async.AsyncBundle#someOf" 44 | )); 45 | 46 | public static boolean isLambdaScaffold(StackTraceElement frame) 47 | { 48 | return frame.getFileName()==null 49 | && frame.getClassName().contains("$$Lambda$"); 50 | } 51 | 52 | public static StackTraceElement[] captureTrace() 53 | { 54 | StackTraceElement[] frames = new Exception().getStackTrace(); 55 | 56 | // bottom of stack: 57 | // async scaffold code 58 | // executor stack 59 | // remove them 60 | int x=frames.length-1; 61 | while( x>=0 && !frames[x].getClassName().equals("bayou.async.Fiber$TaskWrap")) 62 | x--; 63 | if(x==-1) // not executor stack; on some other threads 64 | x = frames.length-1; 65 | 66 | // top of stack: 67 | // async scaffold code 68 | // user code 69 | // async scaffold code 70 | // ... 71 | // remove scaffold 72 | 73 | int count=0; 74 | for(int i=0; i<=x; i++) 75 | { 76 | if(isAsyncScaffold(frames[i])) 77 | { 78 | frames[i] = null; 79 | } 80 | else if(isLambdaScaffold(frames[i])) // noise, not interesting 81 | frames[i] = null; 82 | else 83 | count++; 84 | } 85 | 86 | StackTraceElement[] trace = new StackTraceElement[count]; 87 | count=0; 88 | for(int i=0; i<=x; i++) 89 | if(frames[i]!=null) 90 | trace[count++] = frames[i]; 91 | return trace; 92 | } 93 | 94 | public static void addFiberStackTrace(Throwable exception, Fiber fiber) 95 | { 96 | if(!Fiber.enableTrace) 97 | return; 98 | if(fiber==null) 99 | return; 100 | 101 | StackTraceElement[] trace = fiber.getStackTrace(); 102 | 103 | for(Throwable x : exception.getSuppressed()) 104 | if(x instanceof _Fiber_Stack_Trace_ && covers(x.getStackTrace(), trace)) // avoid duplicates 105 | return; 106 | 107 | _Fiber_Stack_Trace_ traceEx = new _Fiber_Stack_Trace_(); 108 | traceEx.setStackTrace(trace); 109 | exception.addSuppressed(traceEx); 110 | } 111 | 112 | public static boolean covers(StackTraceElement[] a, StackTraceElement[] b) 113 | { 114 | if(a.length=0, "n>=0"); 27 | 28 | toSkip += n; 29 | 30 | toSkip -= origin.skip(toSkip); // may throw 31 | 32 | return n; 33 | } 34 | 35 | @Override 36 | public Async read() 37 | { 38 | if(toSkip==0) // fast path 39 | { 40 | return origin.read(); 41 | } 42 | 43 | return _Asyncs.scan(origin::read, 44 | bb -> 45 | { 46 | long r = (long) bb.remaining(); 47 | if (r <= toSkip) // discard entire bb 48 | { 49 | toSkip -= r; 50 | return null; // read again 51 | } 52 | 53 | // 0<=toSkip 0) 55 | { 56 | bb.position(bb.position() + (int) toSkip); 57 | toSkip = 0; 58 | } 59 | return bb; // loop ends with bb 60 | }, 61 | end -> 62 | { 63 | // if origin EOF, this EOF too. 64 | throw end; 65 | } 66 | ); 67 | } 68 | 69 | 70 | 71 | @Override 72 | public Async close() 73 | { 74 | return origin.close(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/_bayou/_bytes/_EmptyByteSource.java: -------------------------------------------------------------------------------- 1 | package _bayou._bytes; 2 | 3 | import _bayou._tmp._Util; 4 | import bayou.async.Async; 5 | import bayou.bytes.ByteSource; 6 | 7 | import java.nio.ByteBuffer; 8 | 9 | // read() always result in End (before closed) 10 | // an empty source is still stateful (regarding if it's closed) 11 | public class _EmptyByteSource implements ByteSource 12 | { 13 | boolean closed; 14 | 15 | @Override 16 | public Async read() throws IllegalStateException 17 | { 18 | if(closed) 19 | throw new IllegalStateException("closed"); 20 | 21 | return _Util.EOF; 22 | } 23 | 24 | @Override 25 | public Async close() 26 | { 27 | closed = true; 28 | return Async.VOID; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/_bayou/_bytes/_ErrorByteSource.java: -------------------------------------------------------------------------------- 1 | package _bayou._bytes; 2 | 3 | import bayou.async.Async; 4 | import bayou.bytes.ByteSource; 5 | import bayou.util.Result; 6 | 7 | import java.nio.ByteBuffer; 8 | 9 | // read() always result in error (before closed) 10 | // an error source is still stateful (regarding if it's closed) 11 | public class _ErrorByteSource implements ByteSource 12 | { 13 | Async failure; 14 | boolean closed; 15 | 16 | public _ErrorByteSource(Exception exception) 17 | { 18 | this.failure = Result.failure(exception); 19 | } 20 | 21 | @Override 22 | public Async read() throws IllegalStateException 23 | { 24 | if(closed) 25 | throw new IllegalStateException("closed"); 26 | 27 | return failure; 28 | } 29 | 30 | @Override 31 | public Async close() 32 | { 33 | closed = true; 34 | failure = null; 35 | return Async.VOID; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/_bayou/_http/_HttpHostPort.java: -------------------------------------------------------------------------------- 1 | package _bayou._http; 2 | 3 | // host[:port] 4 | // host = domain / ipv4 / "[" ipv6 "]" 5 | 6 | import _bayou._tmp._Dns; 7 | import _bayou._tmp._Ip; 8 | 9 | import java.net.Inet6Address; 10 | import java.net.InetAddress; 11 | 12 | public class _HttpHostPort 13 | { 14 | // either domain or ip is non-null 15 | public String domain; // lower case 16 | public byte[] ip; // 4 or 16 bytes 17 | 18 | public int port = -1; // -1 means missing 19 | 20 | public String toString(int implicitPort) 21 | { 22 | String s=domain; 23 | if(s==null) 24 | { 25 | InetAddress ia = _Ip.toInetAddress(ip); 26 | s = ia.getHostAddress(); 27 | if(ia instanceof Inet6Address) 28 | s = "["+s+"]"; 29 | } 30 | 31 | if(port!=-1 && port!=implicitPort) 32 | s = s + ":" + port; 33 | 34 | return s; 35 | } 36 | 37 | public String hostString() 38 | { 39 | if(domain!=null) 40 | return domain; 41 | else 42 | return _Ip.toInetAddress(ip).getHostAddress(); 43 | } 44 | 45 | public static _HttpHostPort parse(String string) 46 | { 47 | if(string==null || string.isEmpty()) 48 | return null; 49 | 50 | _HttpHostPort hp = new _HttpHostPort(); 51 | 52 | int iPort = string.length(); 53 | if(string.charAt(0)=='[') 54 | { 55 | int x2 = string.lastIndexOf(']'); 56 | if(x2==-1) 57 | return null; 58 | if(x20xFFFF) 99 | return null; 100 | } 101 | 102 | return hp; 103 | } 104 | 105 | // if true, already normalized. 106 | // may return false negative. 107 | public static boolean isNormal(String string, boolean https) 108 | { 109 | if(string==null || string.isEmpty()) return false; 110 | if(string.charAt(0)=='[') return false; // ipv6, later 111 | 112 | int X = string.lastIndexOf(':'); 113 | if(X==-1) 114 | X = string.length(); 115 | else 116 | { 117 | if(X==string.length()-1) return false; // "host:" 118 | if(string.charAt(X+1)=='0') return false; // "host:0808", "host:0" 119 | int port=0; 120 | for(int i=X+1; i0xFFFF) return false; 126 | } 127 | if(port==(https?443:80)) return false; // implicit port; should be removed 128 | } 129 | 130 | return isNormalDomain(string, X) || _Ip.isIpv4(string, 0, X); 131 | 132 | } 133 | 134 | private static boolean isNormalDomain(String string, int X) 135 | { 136 | // domain 137 | if(!_Dns.isValidDomain(string, 0, X)) return false; 138 | // all lower case. no A-Z 139 | for(int i=0; i loggerProvider = 15 | (name) -> new _SimpleLogger(name, _Logger._Level.INFO, System.err); 16 | 17 | static _Logger getLogger(String name) 18 | { 19 | return loggerProvider.apply(name); 20 | } 21 | 22 | 23 | 24 | 25 | static void asyncPrint(_Logger logger, _Logger._Level level, CharSequence message, Throwable throwable) 26 | { 27 | try 28 | { 29 | asyncExec.execute(new Print(logger, level, message, throwable)); 30 | } 31 | catch (RejectedExecutionException e) 32 | { 33 | // logging is shut down. new logs are ignored. 34 | } 35 | } 36 | 37 | static final ThreadPoolExecutor asyncExec = _Exec.newSerialExecutor("Bayou Logging"); 38 | // unbounded queue. will cause out-of-memory if logger is too slow 39 | 40 | // no shutdown hook for last effort of flushing the queue when system exits 41 | 42 | static class Print implements Runnable 43 | { 44 | _Logger logger; 45 | 46 | _Logger._Level level; 47 | CharSequence message; 48 | Throwable throwable; 49 | 50 | String threadName; 51 | 52 | Print(_Logger logger, _Logger._Level level, CharSequence message, Throwable throwable) 53 | { 54 | this.logger = logger; 55 | this.level = level; 56 | this.message = message; 57 | this.throwable = throwable; 58 | 59 | // we capture the thread name, later set it to the thread that invokes the actual logger, 60 | // so that the actual logger can get the thread name in the usual way. 61 | // 62 | // unfortunately we cannot do that with the logging time; the actual logger will see 63 | // a little delayed time. probably not a big deal. 64 | 65 | this.threadName = Thread.currentThread().getName(); 66 | 67 | Fiber fiber = Fiber.current(); 68 | if(fiber!=null) 69 | { 70 | this.threadName += " #fiber# "+fiber.getName(); 71 | 72 | if(Fiber.enableTrace && throwable!=null) 73 | _Fiber_Stack_Trace_.addFiberStackTrace(throwable, fiber); 74 | } 75 | } 76 | 77 | @Override 78 | public void run() // invoke actual logger 79 | { 80 | Thread thread = Thread.currentThread(); 81 | String prevThreadName = thread.getName(); 82 | 83 | { 84 | thread.setName(threadName); 85 | // this will confuse someone reviewing all threads, seeing 2 threads with the same name 86 | } 87 | try 88 | { 89 | logger.impl_log(level, message, throwable); 90 | } 91 | catch (Exception|Error e) 92 | { 93 | e.printStackTrace(); 94 | // all loggers are probably broken. 95 | // maybe we should shutdown asyncExec. 96 | } 97 | finally 98 | { 99 | thread.setName(prevThreadName); 100 | } 101 | 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/_bayou/_log/_SimpleLogger.java: -------------------------------------------------------------------------------- 1 | package _bayou._log; 2 | 3 | import java.io.PrintStream; 4 | 5 | // public, in case user app just wants to change `level` or `out` 6 | public class _SimpleLogger implements _Logger 7 | { 8 | final String name; 9 | final _Level level; 10 | final PrintStream out; 11 | 12 | public _SimpleLogger(String name, _Level level, PrintStream out) 13 | { 14 | this.name = name; 15 | this.level = level; 16 | this.out = out; 17 | } 18 | 19 | @Override 20 | public _Level level() 21 | { 22 | return level; 23 | } 24 | 25 | @Override 26 | public void impl_log(_Level level, CharSequence message, Throwable throwable) 27 | { 28 | out.printf("%1$tF %1$tT.%1$tL [%2$s] %3$-5s %4$s - %5$s%n", 29 | new java.util.Date(), Thread.currentThread().getName(), level, name, message); 30 | 31 | if(throwable!=null) 32 | throwable.printStackTrace(out); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/_bayou/_str/_ByteArr.java: -------------------------------------------------------------------------------- 1 | package _bayou._str; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | 5 | // mutable wrapper of byte array 6 | public final class _ByteArr 7 | { 8 | static final byte[] chars0 = new byte[0]; 9 | 10 | byte[] chars; 11 | int offset; 12 | int length; 13 | int hashCode; 14 | 15 | public _ByteArr() 16 | { 17 | this.chars = chars0; 18 | // offset=length=hashCode=0 19 | } 20 | 21 | public _ByteArr(byte[] chars, int offset, int length) 22 | { 23 | reset(chars, offset, length); 24 | } 25 | 26 | public _ByteArr reset(byte[] chars, int offset, int length) 27 | { 28 | this.chars = chars; 29 | this.offset = offset; 30 | this.length = length; 31 | 32 | int hc = 0; 33 | int L = offset+length; 34 | while(offset implements Consumer 8 | { 9 | public int charCount = 0; 10 | 11 | public _CharSeqSaver(int initialCapacity) 12 | { 13 | super(initialCapacity); 14 | } 15 | 16 | // inheriting ArrayList for impl detail, 17 | // do not use its mutation methods like add() etc 18 | 19 | @Override // Consumer 20 | public void accept(CharSequence csq) 21 | { 22 | super.add(csq); 23 | charCount += csq.length(); 24 | } 25 | 26 | // for method chaining 27 | public _CharSeqSaver append(CharSequence csq) 28 | { 29 | super.add(csq); 30 | charCount += csq.length(); 31 | return this; 32 | } 33 | 34 | public char[] toCharArray() 35 | { 36 | char[] chars = new char[charCount]; 37 | int x=0; 38 | 39 | for(CharSequence csq : this) 40 | for(int i=0; imaxChars) 53 | throw new OverLimitException("maxChars", maxChars); 54 | 55 | if(result.isUnderflow()) 56 | { 57 | if(bb.hasRemaining()) // well this is really retarded. bad bad decoder! 58 | { 59 | leftover = new byte[bb.remaining()]; 60 | bb.get(leftover); 61 | } 62 | return; 63 | } 64 | 65 | assert result.isOverflow(); 66 | // continue 67 | } 68 | } 69 | 70 | public void finish() throws CharacterCodingException, OverLimitException 71 | { 72 | 73 | char[] chars = new char[1024]; 74 | CharBuffer cb = CharBuffer.wrap(chars); 75 | 76 | ByteBuffer in = leftover!=null? ByteBuffer.wrap(leftover) : ByteBuffer.allocate(0); 77 | CoderResult result = decoder.decode(in, cb, true); 78 | if(!result.isUnderflow()) 79 | result.throwException(); 80 | 81 | result = decoder.flush(cb); 82 | if(!result.isUnderflow()) 83 | result.throwException(); 84 | 85 | sb.append(chars, 0, cb.position()); 86 | if(sb.length()>maxChars) 87 | throw new OverLimitException("maxChars", maxChars); 88 | } 89 | 90 | public String getString() 91 | { 92 | return sb.toString(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/_bayou/_str/_HexUtil.java: -------------------------------------------------------------------------------- 1 | package _bayou._str; 2 | 3 | import java.util.Arrays; 4 | 5 | public class _HexUtil 6 | { 7 | 8 | // hex2int['A'] = 0xA 9 | public static int[] hex2int = new int[256]; 10 | static 11 | { 12 | Arrays.fill(hex2int, -1); 13 | for(int i=0; i<10; i++) hex2int['0'+i]=i; 14 | for(int i=0; i< 6; i++) hex2int['A'+i]=hex2int['a'+i]=10+i; 15 | } 16 | 17 | public static int hex2int(char c) 18 | { 19 | if(c>255) 20 | return -1; 21 | else 22 | return hex2int[c]; 23 | } 24 | 25 | // s[i+1] s[i+2] should be HH. return -1 if not. otherwise return 0xHH 26 | public static int hh2int(String string, int L, int i) 27 | { 28 | if(!(i+2> 4) & 0x0f ]; 47 | } 48 | public static char byte2hexLo(byte b) 49 | { 50 | return int2hex[ b & 0x0f ]; 51 | } 52 | 53 | public static String byte2hex(byte b) 54 | { 55 | char[] chars = { byte2hexHi(b), byte2hexLo(b) }; 56 | return new String(chars); 57 | } 58 | 59 | public static String toHexString(byte[] bytes, int start, int length) 60 | { 61 | StringBuilder sb = new StringBuilder(length*2); 62 | for(int i=0; i0; 21 | array = new String[initialCapacity]; 22 | } 23 | 24 | // for method chaining 25 | public _StringSaver append(String str) 26 | { 27 | if(arrayX toList() 56 | { 57 | return new _Array2ReadOnlyList(array, 0, arrayX); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_Array2ReadOnlyList.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | import java.util.AbstractList; 4 | import java.util.List; 5 | 6 | // wrap an array as read-only list 7 | public class _Array2ReadOnlyList extends AbstractList 8 | { 9 | final Object[] sourceArray; 10 | final int offset, size; 11 | 12 | public _Array2ReadOnlyList(Object[] sourceArray) 13 | { 14 | this.sourceArray = sourceArray; 15 | this.offset = 0; 16 | this.size = sourceArray.length; 17 | } 18 | public _Array2ReadOnlyList(Object[] sourceArray, int offset, int size) 19 | { 20 | assert offset+size <= sourceArray.length; 21 | 22 | this.sourceArray = sourceArray; 23 | this.offset = offset; 24 | this.size = size; 25 | } 26 | 27 | public E get(int index) 28 | { 29 | if(index>=size) 30 | throw new IndexOutOfBoundsException(); 31 | Object element = sourceArray[index+offset]; 32 | return _Util.cast(element); 33 | } 34 | public int size() 35 | { 36 | return size; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_ControlException.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | public class _ControlException extends Exception 4 | { 5 | public _ControlException() 6 | { 7 | super(null, null, false, false); 8 | } 9 | public _ControlException(String msg) 10 | { 11 | super(msg, null, false, false); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_CryptoUtil.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | 4 | import _bayou._str._HexUtil; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | import java.security.MessageDigest; 8 | import java.security.NoSuchAlgorithmException; 9 | 10 | public class _CryptoUtil 11 | { 12 | // inputs to string, to UTF8 bytes, md5 on them, salt first if salt!=null 13 | // result is 16 bytes. take 1st n bytes, 1<=n<=16, return hex string 14 | public static String md5(byte[] salt, int n, Object... inputs) 15 | { 16 | try 17 | { 18 | MessageDigest md = MessageDigest.getInstance("MD5"); 19 | 20 | if(salt!=null) 21 | md.update(salt); 22 | 23 | for(Object o : inputs) 24 | { 25 | String s = String.valueOf(o); 26 | byte[] bs = s.getBytes(StandardCharsets.UTF_8); 27 | md.update(bs); 28 | } 29 | 30 | byte[] bytes = md.digest(); 31 | 32 | return _HexUtil.toHexString(bytes, 0, n); 33 | } 34 | catch (NoSuchAlgorithmException e) 35 | { 36 | throw new AssertionError(e); 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_Dns.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | import _bayou._str._CharDef; 4 | import bayou.async.Async; 5 | 6 | import java.net.InetAddress; 7 | 8 | public class _Dns 9 | { 10 | public static boolean isValidDomain(CharSequence domain) 11 | { 12 | return isValidDomain(domain, 0, domain.length()); 13 | } 14 | public static boolean isValidDomain(CharSequence domain, int start, int end) 15 | { 16 | // syntax: 17 | // http://tools.ietf.org/html/rfc1123#page-13 18 | // http://tools.ietf.org/html/rfc1034#section-3.5 19 | // 1 or more labels separated by ".". each label contains 1 or more letter/digit/hyphen. 20 | // each label cannot start/end with hyphen. last label cannot be all digits. 21 | 22 | int state=0; 23 | boolean allDigits=true; 24 | for(int i=start; i0xff) 28 | return false; 29 | 30 | allDigits = allDigits && '0'<=c && c<='9'; 31 | 32 | if(_CharDef.check(c, _CharDef.alphaDigitChars)) 33 | state=1; 34 | else if(c=='-' && state!=0) 35 | state=2; 36 | else if(c=='.' && state==1) 37 | { state=0; allDigits=true; } 38 | else 39 | return false; 40 | } 41 | return state==1 && !allDigits; 42 | } 43 | 44 | public static Async resolve(String domain) 45 | { 46 | // see if it's IP literal 47 | InetAddress x = _Ip.toInetAddress(domain); 48 | if(x!=null) 49 | return Async.success(x); 50 | 51 | return Async.execute( ()->InetAddress.getByName(domain) ); 52 | // this will spawn a new thread 53 | 54 | // todo: actual async dns impl 55 | } 56 | 57 | public static String parent(String domain) 58 | { 59 | int iDot = domain.indexOf('.'); 60 | if(iDot==-1) return null; // domain is top level 61 | return domain.substring(iDot+1); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_JobTimeout.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | import bayou.async.Async; 4 | 5 | import java.time.Duration; 6 | import java.util.concurrent.ScheduledFuture; 7 | import java.util.concurrent.TimeoutException; 8 | 9 | // a timeout util for this use case: 10 | // a job contains a series of async actions. we want to set a timeout for the whole job. 11 | // when timeout is reached, cancel the current async action 12 | public class _JobTimeout implements Runnable 13 | { 14 | int state; 15 | static final int 16 | NEW=0, // ex 17 | TIMING=1, // ex, alarm, [action] 18 | TIMEOUT=2, // ex 19 | COMPLETE=3; // 20 | 21 | String timeoutMessage; 22 | 23 | ScheduledFuture alarm; 24 | 25 | Async currAction; 26 | 27 | 28 | public _JobTimeout(Duration duration, String timeoutMessage) 29 | { 30 | this.timeoutMessage = timeoutMessage; 31 | 32 | // note: `this` is leaked in constructor 33 | ScheduledFuture alarm = _Exec.execNbDelayed(duration, this); 34 | 35 | synchronized (this) 36 | { 37 | // it's possible that alarm has fired 38 | switch (state) 39 | { 40 | case NEW: 41 | this.alarm = alarm; 42 | state = TIMING; 43 | break; 44 | 45 | case TIMEOUT: 46 | break; 47 | 48 | default: throw new AssertionError(); 49 | } 50 | } 51 | } 52 | 53 | // user calls these methods in strict order: 54 | // constructor - 0 or more setCurrAction() - complete() 55 | // alarm can be fired any time 56 | 57 | public void setCurrAction(Async _currAction) 58 | { 59 | String _timeoutMessage; 60 | synchronized (this) 61 | { 62 | switch (state) 63 | { 64 | case TIMING: 65 | currAction = _currAction; 66 | return; 67 | 68 | case TIMEOUT: 69 | _timeoutMessage = timeoutMessage; 70 | break; 71 | 72 | default: throw new AssertionError(); 73 | } 74 | } 75 | 76 | _currAction.cancel(new TimeoutException(_timeoutMessage)); 77 | } 78 | 79 | // job is done. cancel alarm if it has not fired 80 | public void complete() 81 | { 82 | synchronized (this) 83 | { 84 | switch (state) 85 | { 86 | case TIMING: 87 | state = COMPLETE; 88 | break; 89 | // COMPLETE is final state, so we can do the rest of the work outside sync{} 90 | 91 | case TIMEOUT: 92 | timeoutMessage = null; 93 | state = COMPLETE; 94 | return; 95 | 96 | default: throw new AssertionError(); 97 | } 98 | } 99 | 100 | alarm.cancel(true); 101 | 102 | alarm = null; 103 | timeoutMessage = null; 104 | currAction = null; 105 | } 106 | 107 | 108 | // alarm is fired 109 | public void run() 110 | { 111 | Async _currAction; 112 | String _timeoutMessage; 113 | 114 | synchronized (this) 115 | { 116 | switch(state) 117 | { 118 | case COMPLETE: 119 | return; 120 | 121 | case NEW: 122 | state = TIMEOUT; 123 | return; 124 | 125 | case TIMING: 126 | state = TIMEOUT; 127 | alarm = null; 128 | // cancel curr action 129 | _currAction = currAction; 130 | if(_currAction==null) 131 | return; 132 | currAction = null; 133 | _timeoutMessage = timeoutMessage; 134 | break; 135 | 136 | default: throw new AssertionError(); 137 | } 138 | } 139 | 140 | _currAction.cancel(new TimeoutException(_timeoutMessage)); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_KnownHeaders.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | import _bayou._str._ByteArr; 4 | import _bayou._str._ChArrCi; 5 | import _bayou._str._StrCi; 6 | import bayou.mime.Headers; 7 | 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.Modifier; 10 | import java.util.HashMap; 11 | 12 | public class _KnownHeaders 13 | { 14 | static final HashMap map0 = new HashMap<>(256); 15 | static final HashMap<_ChArrCi,String> map1 = new HashMap<>(256); 16 | static final HashMap<_StrCi,String> map2 = new HashMap<>(256); 17 | static final HashMap<_ByteArr,String> map3 = new HashMap<>(256); 18 | 19 | static 20 | { 21 | final int modPublicStaticFinal = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL; 22 | for(Field field : Headers.class.getDeclaredFields()) 23 | { 24 | if((field.getModifiers() & modPublicStaticFinal) != modPublicStaticFinal) 25 | continue; 26 | if(field.getType()!=String.class) 27 | continue; 28 | // public static final String 29 | String s; 30 | try{ s = (String)field.get(null); } catch(Exception e){ throw new AssertionError(e); } 31 | 32 | map0.put(s, s); 33 | _ChArrCi k1 = new _ChArrCi(s.toCharArray()); 34 | map1.put(k1, s); 35 | _StrCi k2 = new _StrCi(s); 36 | map2.put(k2, s); 37 | 38 | _ByteArr k3 = _ByteArr.of(s); 39 | map3.put(k3, s); 40 | } 41 | } 42 | // look up a well-known header. chars may be in different cases. 43 | public static String lookup(char[] chars, int length) 44 | { 45 | _ChArrCi k1 = new _ChArrCi(chars, 0, length); 46 | return map1.get(k1); 47 | } 48 | public static String lookup(String s) 49 | { 50 | String v = map0.get(s); // likely to succeed, with s==key 51 | if(v!=null) 52 | return v; 53 | 54 | _StrCi k2 = new _StrCi(s); 55 | return map2.get(k2); // unlikely to succeed(s is known header but in diff cases ) 56 | } 57 | public static String lookup(_ByteArr k3) 58 | { 59 | return map3.get(k3); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.function.Supplier; 5 | 6 | public class _NamedThreadFactory implements ThreadFactory 7 | { 8 | final Supplier nameSupplier; 9 | 10 | public _NamedThreadFactory(final String name) 11 | { 12 | this(() -> name); 13 | } 14 | 15 | public _NamedThreadFactory(Supplier nameSupplier) 16 | { 17 | this.nameSupplier = nameSupplier; 18 | } 19 | 20 | @Override public Thread newThread(Runnable r) 21 | { 22 | Thread thread = new Thread(r, nameSupplier.get()); 23 | // it inherits current thread's daemon-ness, priority, and thread-group. 24 | // need to reset the first two. thread groups are obsolete so we don't care. 25 | thread.setDaemon(false); 26 | thread.setPriority(Thread.NORM_PRIORITY); 27 | return thread; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_PrefixedMsgOut.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | import _bayou._str._CharSubSeq; 4 | 5 | import java.io.PrintStream; 6 | import java.util.function.Consumer; 7 | 8 | // prefix each line with a prefix string 9 | public class _PrefixedMsgOut implements Consumer 10 | { 11 | final Consumer out; 12 | final String prefix; 13 | 14 | public _PrefixedMsgOut(Consumer out, String prefix) 15 | { 16 | this.out = out; 17 | this.prefix = prefix; 18 | } 19 | 20 | public _PrefixedMsgOut(PrintStream out, String prefix) 21 | { 22 | this(out::println, prefix); 23 | } 24 | 25 | @Override 26 | public void accept(CharSequence msg) 27 | { 28 | int i1=0; 29 | int i2=0; 30 | int L = msg.length(); 31 | while(i2 - normal. e.g. "com", "co.uk" 17 | // *. - star. e.g. "*.ck" 18 | // ! - exception. e.g. "!www.ck" 19 | // is normal domain, containing no "*". it's lower case, A-Label or NR-LDH. 20 | // a domain can only be mapped to one type (or none) 21 | 22 | enum Type { normal, exception, star } 23 | static final HashMap rules = new HashMap<>(); // domain->type 24 | static 25 | { 26 | try 27 | { 28 | loadRules(); 29 | } 30 | catch (Exception e) 31 | { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | static void loadRules() throws Exception 37 | { 38 | try( InputStream r1 = _PublicSuffix.class.getResourceAsStream("/_bayou/__resource/public.suffix.txt") ; 39 | Reader r2 = new InputStreamReader( r1, StandardCharsets.UTF_8); 40 | BufferedReader r3 = new BufferedReader(r2) ) 41 | { 42 | r3.lines().forEach(line-> 43 | { 44 | try 45 | { 46 | addRuleLine(line); 47 | } 48 | catch (Exception e) 49 | { 50 | throw new RuntimeException("unexpected rule: "+line, e); 51 | } 52 | }); 53 | } 54 | } 55 | static void addRuleLine(String line) throws Exception 56 | { 57 | line = line.trim(); 58 | if (line.isEmpty() || line.startsWith("//")) 59 | return; 60 | 61 | Type type; 62 | String domain; 63 | if(line.startsWith("*.")) 64 | { 65 | type = Type.star; 66 | domain = line.substring(2); 67 | } 68 | else if(line.startsWith("!")) 69 | { 70 | type = Type.exception; 71 | domain = line.substring(1); 72 | // [assumption] exception rule contains at least 2 labels 73 | if(!domain.contains(".")) 74 | throw new Exception(); 75 | } 76 | else 77 | { 78 | type = Type.normal; 79 | domain = line; 80 | } 81 | 82 | // [assumption] there is at most one "*", at the left-most level, followed by more labels. 83 | if(domain.contains("*")) 84 | throw new RuntimeException(); 85 | 86 | domain = IDN.toASCII(domain).toLowerCase(); 87 | 88 | // [assumption] one rule for each domain. no cases like { "*.x.y" and "x.y" } 89 | if(rules.containsKey(domain)) 90 | throw new Exception(); 91 | 92 | rules.put(domain, type); 93 | } 94 | 95 | 96 | // ========================================================================================================== 97 | 98 | // the input is in canonical form - lower case, A-Label or NR-LDH. 99 | public static boolean isPublicSuffix(String domain) 100 | { 101 | Type type = rules.get(domain); 102 | if(type==null) 103 | { 104 | String parent = _Dns.parent(domain); 105 | if(parent==null) // domain is unknown TLD. 106 | return true; // treat all TLDs as public suffix, as far as cookie is concerned 107 | return rules.get(parent)==Type.star; // *.parent 108 | } 109 | switch(type) 110 | { 111 | case normal : return true; 112 | case exception: return false; 113 | 114 | // rule [*.domain] - domain is not public suffix, unless it's TLD 115 | case star: return domain.indexOf('.')==-1; 116 | 117 | default: throw new AssertionError(); 118 | } 119 | 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_StreamIter.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | import _bayou._log._Logger; 4 | 5 | import java.util.Spliterator; 6 | import java.util.function.Consumer; 7 | import java.util.function.Supplier; 8 | import java.util.stream.Stream; 9 | 10 | public class _StreamIter implements Consumer 11 | { 12 | final Stream stream; 13 | final Spliterator iter; 14 | 15 | public _StreamIter(Stream stream) 16 | { 17 | this.stream = stream; 18 | this.iter = stream.spliterator(); 19 | } 20 | 21 | T element; 22 | @Override 23 | public void accept(T t) 24 | { 25 | element = t; 26 | } 27 | 28 | // return null if no more 29 | public T next() 30 | { 31 | if(!iter.tryAdvance(this)) 32 | { 33 | // close(); // unnecessary. user should call close() 34 | return null; 35 | } 36 | 37 | if(element==null) 38 | throw new NullPointerException("stream contains null element"); 39 | 40 | T tmp = element; 41 | element = null; 42 | return tmp; 43 | } 44 | 45 | public void close() 46 | { 47 | try 48 | { 49 | stream.close(); 50 | } 51 | catch (Exception e) 52 | { 53 | _Logger.of(_StreamIter.class) 54 | .error("Exception from Stream.close(), stream=%s, exception=%s", stream, e); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_TcpConn2Chann.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | import bayou.async.Async; 4 | import bayou.tcp.TcpChannel; 5 | import bayou.tcp.TcpConnection; 6 | 7 | import java.net.InetAddress; 8 | import java.nio.ByteBuffer; 9 | import java.util.concurrent.Executor; 10 | 11 | // implemented by a TcpConnection to convert itself to TcpChannel 12 | // this is used internally for SSL over SSL. 13 | // since SslConnectionImpl is done over TcpChannel, and we don't want to touch that, 14 | // we need to wrap TcpConnection/SslConnectionImpl as a TcpChannel. 15 | // SSL over SSL is rare, so don't worry too much about perfection. 16 | public interface _TcpConn2Chann 17 | { 18 | // called on a TcpConnection only when there's no unread or queued writes. 19 | public TcpChannel toChann(String peerHost, int peerPort); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/_bayou/_tmp/_TrafficDumpWrapper.java: -------------------------------------------------------------------------------- 1 | package _bayou._tmp; 2 | 3 | import _bayou._tmp._Exec; 4 | 5 | import java.util.List; 6 | import java.util.concurrent.RejectedExecutionException; 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | import java.util.function.Consumer; 9 | 10 | // `dump` is invoked on a single thread. preventing interleaving outputs. 11 | public class _TrafficDumpWrapper 12 | { 13 | public final String name; 14 | public final Consumer dump; 15 | 16 | // 1 thread, not daemon, expires after 10sec. unbounded queue. tasks are serialized 17 | final ThreadPoolExecutor serialExec; 18 | 19 | public _TrafficDumpWrapper(String name, Consumer dump) 20 | { 21 | this.name = name; 22 | this.dump = dump; 23 | 24 | serialExec = _Exec.newSerialExecutor(name+" Traffic Dump"); 25 | } 26 | 27 | public void print(CharSequence... chars) 28 | { 29 | print(new _Array2ReadOnlyList<>(chars)); 30 | } 31 | 32 | public void print(List chars) 33 | { 34 | Runnable task = () -> 35 | { 36 | try 37 | { 38 | for(CharSequence cs : chars) 39 | dump.accept(cs); 40 | } 41 | catch (Error | RuntimeException e ) // printer is broken 42 | { 43 | serialExec.shutdownNow(); 44 | 45 | System.err.println(name+" trafficDump is broken: "+e); 46 | e.printStackTrace(); // dump to console instead of to logger 47 | } 48 | }; 49 | 50 | try 51 | { 52 | serialExec.execute(task); 53 | } 54 | catch (RejectedExecutionException e) 55 | { 56 | // ok. printer was broken. new logs are ignored. 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/_bayou/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * internal stuff. not publicized. 3 | */ 4 | package _bayou; -------------------------------------------------------------------------------- /src/bayou/async/AsyncTimeout.java: -------------------------------------------------------------------------------- 1 | package bayou.async; 2 | 3 | import _bayou._async._Asyncs; 4 | import _bayou._tmp._Exec; 5 | import bayou.util.Result; 6 | 7 | import java.time.Duration; 8 | import java.util.concurrent.ScheduledFuture; 9 | import java.util.concurrent.TimeoutException; 10 | import java.util.function.Consumer; 11 | import java.util.function.Supplier; 12 | 13 | // for impl Async.timeout() 14 | 15 | // Currently we use ScheduledThreadPoolExecutor for timeout. 16 | // We may be able to create a better tool. 17 | // In most timeout(duration) calls, duration is one of several fixed values. 18 | // For example, we call timeout(server.confKeepAliveTimeout) a lot. 19 | // We can have a very simple data structure for timeouts of the same duration. 20 | // Another fact: most promise completes way before timeout is reached, 21 | // for example, socket read/write timeout. we may optimize for that to save space/time. 22 | // 23 | // Currently, ScheduledThreadPoolExecutor seems fine, 24 | // so we leave improvement for future versions. 25 | 26 | class AsyncTimeout implements Consumer>, Runnable 27 | { 28 | 29 | Async target; 30 | 31 | Duration duration; 32 | Supplier exSupplier; // may be null 33 | 34 | ScheduledFuture alarm; 35 | 36 | public AsyncTimeout(Async target, Duration duration, Supplier exSupplier) 37 | { 38 | this.target = target; 39 | 40 | this.duration = duration; 41 | this.exSupplier = exSupplier; 42 | 43 | ScheduledFuture alarm = _Exec.execNbDelayed(duration, this/*run()*/); 44 | // note: `this` is leaked. timeout could reach at any time, even before [A] 45 | this.alarm = alarm; // [A] 46 | 47 | _Asyncs.onCompletion(target, Runnable::run, this/*accept()*/); 48 | // note: `this` is leaked 49 | } 50 | 51 | 52 | // we don't need precise atomic state management here; 53 | // it's OK if both events arrive and both are processed. 54 | // 55 | // if timeout is reached before completion (which should be rare) 56 | // run()->target.cancel()->this.accept()->alarm.cancel() 57 | // here, alarm.cancel() is unnecessary; but it's cheap, just a volatile read. 58 | 59 | 60 | @Override // Consumer> // target is completed // after [A] 61 | public void accept(Result result) 62 | { 63 | alarm.cancel(false); 64 | } 65 | 66 | @Override // Runnable // timeout event // may arrive before [A] 67 | public void run() 68 | { 69 | Exception ex = exSupplier!=null? exSupplier.get() : 70 | new TimeoutException(msg(duration)); 71 | 72 | target.cancel(ex); 73 | } 74 | 75 | static String msg(Duration duration){ return "duration="+duration.toString().substring(2).toLowerCase(); } 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/bayou/async/AutoAsync.java: -------------------------------------------------------------------------------- 1 | package bayou.async; 2 | 3 | import _bayou._async._Asyncs; 4 | import bayou.util.Result; 5 | 6 | import java.time.Duration; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * To make a type also as an `Async` type. 11 | *

12 | * Often we need to convert a `Foo` object to an `Async<Foo>` object. 13 | * This can be done by `Async.success(foo)`; but it can become tedious if it happens a lot. 14 | *

15 | *

16 | * By simply `extends/implements AutoAsync<Foo>`, we can make an `interface/class Bar` 17 | * a subtype of `Async<Foo>`, 18 | * then a `Bar` object can be used anywhere an `Async<Foo>` is needed. 19 | * Note that `AutoAsync` is an interface with no abstract method. 20 | *

21 | *

22 | * Requirement: if `interface/class Bar extends/implements AutoAsync<Foo>`, 23 | * it must be true that `Bar` is `Foo` or a subtype of `Foo`. 24 | *

25 | *

26 | * Caution: Bar inherits all instance methods from Async<Foo>, 27 | * which can create confusions along with Bar's own methods. 28 | *

29 | *

30 | * One usage example is `HttpResponseImpl`: 31 | *

32 | *
 33 |  *     public class HttpResponseImpl implements HttpResponse, AutoAsync<HttpResponse>
 34 |  * 
35 | *

36 | * this means that an HttpResponseImpl object can be used any where an Async<HttpResponse> is needed. 37 | *

38 | */ 39 | public interface AutoAsync extends Async 40 | { 41 | // It may seem that it's better to have AutoResult instead, 42 | // because each Foo is conceptually a Result.Success. (and pollResult() will be cheaper) 43 | // however methods of Result probably create more confusions than methods of Async 44 | // if they blend in Foo 's methods. 45 | // and we rarely need Foo as Result; we usually need Foo as Async 46 | 47 | 48 | 49 | /** 50 | * Implements {@link Async#pollResult()}. 51 | * Equivalent to `Result.success( (T)this) )` 52 | */ 53 | @Override 54 | public default Result pollResult() 55 | { 56 | @SuppressWarnings("unchecked") 57 | T thisT = (T)this; 58 | return Result.success(thisT); 59 | } 60 | 61 | /** return `true` */ 62 | @Override 63 | public default boolean isCompleted() 64 | { 65 | return true; 66 | } 67 | 68 | /** 69 | * Implements {@link Async#onCompletion(Consumer)}. 70 | * Note that `this` is already completed. 71 | */ 72 | @Override 73 | public default void onCompletion(Consumer> callback) 74 | { 75 | _Asyncs.bindToCurrExec(callback).accept(pollResult()); 76 | // async-ly. usually invoked later on the current thread. 77 | } 78 | 79 | /** 80 | * Implements {@link Async#cancel(Exception)}; 81 | * by default, do nothing. 82 | */ 83 | @Override 84 | public default void cancel(Exception reason) 85 | { 86 | // no effect 87 | } 88 | 89 | 90 | /** 91 | * Implements {@link Async#timeout(Duration)}; 92 | * by default, do nothing. 93 | * Returns `this`. 94 | * 95 | * @return `this` 96 | */ 97 | @Override 98 | public default Async timeout(Duration duration) 99 | { 100 | // no effect 101 | return this; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/bayou/async/CallbackList.java: -------------------------------------------------------------------------------- 1 | package bayou.async; 2 | 3 | import bayou.util.Result; 4 | 5 | import java.util.ArrayList; 6 | import java.util.function.Consumer; 7 | 8 | interface CallbackList extends Consumer> 9 | { 10 | CallbackList concat(Consumer> callback); 11 | 12 | void registerTo(Async async); 13 | 14 | 15 | 16 | static final boolean warn =false; // if true, report if there are more than 1 callbacks 17 | 18 | // may modify callbacks and return it 19 | public static Consumer> concat(Consumer> callbacks, Consumer> callback) 20 | { 21 | if(callbacks==null) 22 | return callback; 23 | 24 | if(callbacks instanceof CallbackList) 25 | return ((CallbackList)callbacks).concat(callback); 26 | 27 | if(warn) System.out.println("num of callbacks = "+2); 28 | return new L2<>(callbacks, callback); 29 | } 30 | 31 | // for each callback, do async2.onCompletion(callback) 32 | public static void registerTo(Consumer> callbacks, Async async) 33 | { 34 | if(callbacks==null) 35 | ; 36 | else if(callbacks instanceof CallbackList) 37 | ((CallbackList)callbacks).registerTo(async); 38 | else 39 | async.onCompletion(callbacks); 40 | } 41 | 42 | // L2 is probably common. 43 | static class L2 implements CallbackList 44 | { 45 | final Consumer> callback1, callback2; 46 | 47 | L2(Consumer> callback1, Consumer> callback2) 48 | { 49 | this.callback1 = callback1; 50 | this.callback2 = callback2; 51 | } 52 | 53 | @Override 54 | public void accept(Result result) 55 | { 56 | callback1.accept(result); 57 | callback2.accept(result); 58 | } 59 | 60 | @Override 61 | public CallbackList concat(Consumer> callback) 62 | { 63 | if(warn) System.out.println("num of callbacks = "+3); 64 | return new Lx<>(callback1, callback2, callback); 65 | } 66 | 67 | @Override 68 | public void registerTo(Async async) 69 | { 70 | async.onCompletion(callback1); 71 | async.onCompletion(callback2); 72 | } 73 | } 74 | 75 | static class Lx extends ArrayList>> implements CallbackList 76 | { 77 | public Lx(Consumer> callback1, Consumer> callback2, Consumer> callback3) 78 | { 79 | add(callback1); 80 | add(callback2); 81 | add(callback3); 82 | } 83 | 84 | @Override 85 | public CallbackList concat(Consumer> callback) 86 | { 87 | if(warn) System.out.println("num of callbacks = "+ (size()+1) ); 88 | add(callback); 89 | return this; 90 | } 91 | 92 | @Override 93 | public void accept(Result result) 94 | { 95 | for(Consumer> callback : this) 96 | callback.accept(result); 97 | } 98 | 99 | @Override 100 | public void registerTo(Async async) 101 | { 102 | for(Consumer> callback : this) 103 | async.onCompletion(callback); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/bayou/async/FiberLocal.java: -------------------------------------------------------------------------------- 1 | package bayou.async; 2 | 3 | import _bayou._tmp._Util; 4 | 5 | /** 6 | * A fiber-local variable. This class is an analogue of `ThreadLocal`. 7 | *

8 | * Example usage: 9 | *

10 | *
11 |      static final FiberLocal<User> fiberLocalUser = new FiberLocal<>();
12 | 
13 |      Async<Void> action1()
14 |      {
15 |          fiberLocalUser.set(user);
16 |          return Fiber.sleep(Duration.ofSeconds(1))
17 |              .then( v->action2() );
18 |      }
19 |      Async<Void> action2()
20 |      {
21 |          User user = fiberLocalUser.get();
22 |          ...
23 |      }
24 |  * 
25 | *

26 | * The initial fiber-local value of a FiberLocal is always null. 27 | *

28 | */ 29 | public class FiberLocal 30 | { 31 | /** 32 | * Create a FiberLocal variable. 33 | * The result object is usually assigned to a static final variable. 34 | */ 35 | public FiberLocal() 36 | { 37 | 38 | } 39 | 40 | /** 41 | * Get the fiber-local value for this variable. 42 | * 43 | *

44 | * The initial fiber-local value, before any {@link #set set()} calls, is null. 45 | *

46 | * 47 | * @throws IllegalStateException 48 | * if `Fiber.current()==null` 49 | */ 50 | public T get() throws IllegalStateException 51 | { 52 | Fiber fiber = Fiber.current(); 53 | if(fiber==null) 54 | throw new IllegalStateException("Fiber.current()==null"); 55 | Object value = fiber.fiberLocalMap.get(this); 56 | return _Util.cast(value); 57 | } 58 | 59 | /** 60 | * Set the fiber-local value for this variable. 61 | * 62 | * @throws IllegalStateException 63 | * if `Fiber.current()==null` 64 | */ 65 | // return void - don't know whether to return prev value or the new value. 66 | public void set(T value) throws IllegalStateException 67 | { 68 | Fiber fiber = Fiber.current(); 69 | if(fiber==null) 70 | throw new IllegalStateException("Fiber.current()==null"); 71 | if(value==null) 72 | fiber.fiberLocalMap.remove(this); 73 | else 74 | fiber.fiberLocalMap.put(this, value); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/bayou/async/FlatMapIterator.java: -------------------------------------------------------------------------------- 1 | package bayou.async; 2 | 3 | import _bayou._tmp._Util; 4 | import bayou.util.End; 5 | import bayou.util.Result; 6 | import bayou.util.function.FunctionX; 7 | 8 | class FlatMapIterator implements AsyncIterator 9 | { 10 | AsyncIterator streamT; // null if it's ended 11 | FunctionX> func; 12 | FunctionX> endFunc; 13 | 14 | AsyncIterator streamR; 15 | 16 | FlatMapIterator(AsyncIterator streamT, FunctionX> func, FunctionX> endFunc) 17 | { 18 | this.streamT = streamT; 19 | this.func = func; 20 | this.endFunc = endFunc; 21 | } 22 | 23 | @Override 24 | public Async next() 25 | { 26 | if(streamR!=null) 27 | return nextR(); 28 | else 29 | return nextT(); 30 | } 31 | 32 | // tail recursion: nextR()->nextT()->nextR()->... 33 | 34 | // cancellation: relies on streamR/streamT.next() to respond to cancellation requests. 35 | 36 | Async nextR() 37 | { 38 | Async nextR = streamR.next(); 39 | 40 | if(streamT==null) // no more T 41 | return nextR; 42 | 43 | return nextR.transform(resultR-> 44 | { 45 | Exception ex = resultR.getException(); 46 | if(!(ex instanceof End)) // null or other exception 47 | return resultR; 48 | // streamR ended. 49 | streamR=null; 50 | return nextT(); 51 | }); 52 | } 53 | 54 | Async nextT() 55 | { 56 | Async nextT = streamT.next(); 57 | 58 | return nextT.transform(resultT -> 59 | { 60 | Exception ex = resultT.getException(); 61 | if (ex == null) 62 | { 63 | try 64 | { 65 | streamR = func.apply(resultT.getValue()); // throws 66 | } 67 | catch (End end) 68 | { 69 | ex = end; 70 | } 71 | if(ex==null) 72 | return nextR(); 73 | // else, feed end to endFunc 74 | } 75 | 76 | assert ex!=null; 77 | if (ex instanceof End) 78 | { 79 | streamR = endFunc.apply((End) ex); // throws 80 | streamT = null; 81 | func = null; 82 | endFunc = null; 83 | return nextR(); 84 | } 85 | else // other exception 86 | { 87 | return _Util.>cast(resultT); // can cast because of erasure 88 | } 89 | }); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/bayou/async/ForEachNoAsync.java: -------------------------------------------------------------------------------- 1 | package bayou.async; 2 | 3 | import _bayou._async._AsyncDoWhile; 4 | import bayou.util.End; 5 | import bayou.util.Result; 6 | import bayou.util.function.ConsumerX; 7 | import bayou.util.function.FunctionX; 8 | 9 | import static _bayou._async._Asyncs._next; 10 | 11 | class ForEachNoAsync extends _AsyncDoWhile implements FunctionX> 12 | { 13 | static final ThreadLocal> threadLocal = new ThreadLocal<>(); 14 | 15 | final AsyncIterator iter; 16 | final ConsumerX consumer; 17 | boolean warned; 18 | 19 | public ForEachNoAsync(AsyncIterator iter, ConsumerX consumer) 20 | { 21 | this.iter = iter; 22 | this.consumer = consumer; 23 | } 24 | 25 | @Override 26 | protected Async action() 27 | { 28 | return _next(iter) 29 | .then(/*FunctionX*/this); 30 | } 31 | 32 | @Override 33 | protected Result condition(Result result) 34 | { 35 | Exception e = result.getException(); 36 | if(e==null) 37 | return null; // continue loop 38 | if(e instanceof End) 39 | return Asyncs.successVoid; // loop ends with success 40 | else 41 | return result; // loop ends with failure 42 | } 43 | 44 | 45 | @Override // FunctionX 46 | public Async apply(T t) throws Exception 47 | { 48 | ForEachNoAsync prev = threadLocal.get(); 49 | threadLocal.set(this); 50 | try 51 | { 52 | consumer.accept(t); 53 | // it should not involve any async actions. 54 | // if it creates Promise/AsyncThen, we'll issue a warning, see warn() 55 | // the overhead of thread local seems tiny, relative to the overhead of _AsyncDoWhile 56 | } 57 | finally 58 | { 59 | threadLocal.set(prev); 60 | } 61 | return Async.VOID; 62 | } 63 | 64 | // invoked by Promise/AsyncThen constructors 65 | static void warn() 66 | { 67 | ForEachNoAsync x = threadLocal.get(); 68 | if(x!=null && !x.warned) 69 | { 70 | x.warned=true; 71 | new Exception("WARNING: AsyncIterator.forEach(action): `action` appears to be ASYNC. " + 72 | "Try forEach_(asyncAction) instead.") 73 | .printStackTrace(); // dump to console instead of to logger 74 | 75 | // it is extremely dangerous to pass an async action to forEach(), so we must issue a warning here. 76 | 77 | // we don't provide a way to disable this warning, 78 | // if the programmer has a legitimate use case, use forEach_(asyncAction) instead 79 | // (return Async.VOID in asyncAction) 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/bayou/async/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Async programming model. 3 | */ 4 | package bayou.async; -------------------------------------------------------------------------------- /src/bayou/bytes/ByteSink.java: -------------------------------------------------------------------------------- 1 | package bayou.bytes; 2 | 3 | import bayou.async.Async; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | /** 8 | * Async sink of bytes. 9 | *

10 | * Bytes are written to the sink through method {@link #write(ByteBuffer)}. 11 | * The sink must be closed after use. 12 | *

13 | *

14 | * The writer should also call {@link #error(Exception)} to indicate that the byte sequence is corrupt. 15 | *

16 | *

17 | * In general, a ByteSink is not thread-safe. 18 | *

19 | *

20 | * In general, concurrent pending actions are not allowed. 21 | * Particularly, close() cannot be invoked while a write() is pending. 22 | *

23 | */ 24 | public interface ByteSink 25 | { 26 | /** 27 | * Write the bytes to this sink. 28 | *

29 | * If this action fails, the sink should be considered in an error state, 30 | * and it should be closed immediately. 31 | *

32 | *

33 | * The ownership of `bb` is transferred to the sink. 34 | * The sink should treat the content of `bb` as read-only. 35 | *

36 | *

37 | * CAUTION: since ByteBuffer is stateful (even for methods like {@link ByteBuffer#get()}), 38 | * a new ByteBuffer must be created for each write() action. 39 | * The caller may create a view of a shared ByteBuffer through 40 | * {@link java.nio.ByteBuffer#asReadOnlyBuffer()}. 41 | *

42 | *

43 | * The app should wait for this write() action to complete 44 | * before it calls another method on this sink. 45 | *

46 | */ 47 | Async write(ByteBuffer bb); 48 | 49 | /** 50 | * Set this sink to an error state. 51 | *

52 | * If the data producer encounters an error, it should set the sink to an error state, 53 | * so that the situation is not confused with a graceful termination of writing. 54 | *

55 | *

56 | * For example, a server app calculates data and writes them to an http response sink. 57 | * If an exception occurs in the calculation, the app should call sink.error(), 58 | * so that the client can know that the response body is corrupt. 59 | *

60 | *

61 | * If the sink is in an error state, it should be closed immediately. 62 | *

63 | *

64 | * This method can be called multiple times; only the first call is effective. 65 | *

66 | */ 67 | Async error(Exception error); 68 | // usually this action should not fail 69 | // this method is useful, e.g. in HTTP/1.1 chunked response. if app encounters an error while 70 | // producing response body, sink.error() will cause the connection to terminate, client 71 | // will detect that the response body is incomplete (because there's no last-chunk) 72 | // without sink.error(), server/client do not know that the body is incomplete. 73 | 74 | /** 75 | * Close this sink. 76 | *

77 | * If the sink is not in an error state, 78 | * close() should flush all previously written data, and wait for the flushing to complete. 79 | *

80 | *

81 | * The close() action is important to finalize the writing process. 82 | * If the close() action fails, it may indicate that 83 | * the data are not reliably delivered to the destination. 84 | *

85 | *

86 | * This method can be called multiple times; only the first call is effective. 87 | *

88 | */ 89 | Async close(); 90 | // unlike ByteSource.close(), 91 | // this close() has important semantics. it may be pending. may fail. 92 | // caller cares about the result. may want to cancel a pending close. 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/bayou/bytes/PushbackByteSource.java: -------------------------------------------------------------------------------- 1 | package bayou.bytes; 2 | 3 | import _bayou._tmp._Util; 4 | import bayou.async.Async; 5 | import bayou.util.Result; 6 | 7 | import java.nio.ByteBuffer; 8 | 9 | /** 10 | * A ByteSource wrapper that supports unread(). 11 | * 12 | *

13 | * `unread(bb)` stores `bb` internally to be served in the next `read()`. 14 | * read() will remove the stored `bb`. skip() may also remove the stored `bb`. 15 | *

16 | *

17 | * At most one ByteBuffer can be stored at a time. 18 | * Consecutive unread() calls are not supported. 19 | *

20 | */ 21 | public class PushbackByteSource implements ByteSource 22 | { 23 | ByteSource origin; 24 | 25 | ByteBuffer hoard; 26 | // at this impl, only 1 hoard is supported. that's enough for most apps. 27 | 28 | boolean closed; 29 | 30 | /** 31 | * Create a PushbackByteSource wrapper of the `origin` ByteSource. 32 | */ 33 | public PushbackByteSource(ByteSource origin) 34 | { 35 | this.origin = origin; 36 | } 37 | 38 | /** 39 | * Unread `bb`. 40 | *

41 | * `bb` is stored, to be served in then next read(). 42 | *

43 | *

44 | * If there is already a ByteBuffer stored from a previous unread(), 45 | * this method throws IllegalStateException. 46 | *

47 | * 48 | * @throws java.lang.IllegalStateException 49 | * if there is already a ByteBuffer stored from a previous unread(); 50 | * or if this source has been closed. 51 | */ 52 | public void unread(ByteBuffer bb) throws IllegalStateException 53 | { 54 | if(closed) 55 | throw new IllegalStateException("closed"); 56 | 57 | if(hoard !=null) 58 | throw new IllegalStateException("consecutive unread() not supported"); 59 | 60 | // bb can be empty, we'll still hoard it and return it in next read() 61 | hoard = bb; 62 | } 63 | 64 | /** 65 | * Read the next chunk of bytes. 66 | *

67 | * If there is a `bb` stored from a previous unread(), 68 | * it's removed, and this read() action succeeds immediately with that `bb`. 69 | * Otherwise, this call is equivalent to `origin.read()`. 70 | *

71 | * @throws IllegalStateException 72 | * if this source is closed. 73 | */ 74 | @Override 75 | public Async read() throws IllegalStateException 76 | { 77 | if(closed) 78 | throw new IllegalStateException("closed"); 79 | 80 | ByteBuffer hoardL = hoard; 81 | if(hoardL!=null) 82 | { 83 | hoard = null; 84 | return Result.success(hoardL); 85 | } 86 | 87 | return origin.read(); 88 | } 89 | 90 | /** 91 | * Try to skip forward `n` bytes. 92 | *

93 | * If there is a `bb` stored from a previous unread(), 94 | * its position may be adjusted, or it may be removed entirely. 95 | *

96 | * 97 | * @throws IllegalStateException 98 | * if this source is closed. 99 | */ 100 | @Override 101 | public long skip(long n) throws IllegalArgumentException, IllegalStateException 102 | { 103 | _Util.require(n >= 0, "n>=0"); 104 | 105 | if(closed) 106 | throw new IllegalStateException("closed"); 107 | 108 | if(hoard==null) 109 | return origin.skip(n); 110 | 111 | int s = hoard.remaining(); 112 | if(ns 123 | return s + origin.skip(n-s); 124 | } 125 | 126 | /** 127 | * Close this source. 128 | */ 129 | @Override 130 | public Async close() 131 | { 132 | if(closed) 133 | return Async.VOID; 134 | closed = true; 135 | 136 | if(hoard !=null) 137 | { 138 | hoard = null; 139 | } 140 | 141 | origin.close(); 142 | origin=null; 143 | return Async.VOID; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/bayou/bytes/SimpleByteSource.java: -------------------------------------------------------------------------------- 1 | package bayou.bytes; 2 | 3 | import _bayou._tmp._StreamIter; 4 | import _bayou._tmp._Util; 5 | import bayou.async.Async; 6 | import bayou.util.Result; 7 | 8 | import java.nio.ByteBuffer; 9 | import java.util.stream.Stream; 10 | 11 | /** 12 | * A simple ByteSource based on some ByteBuffers. 13 | *

14 | * Each SimpleByteSource is associated with a `Stream<ByteBuffer>`. 15 | * The read() method simply serves the ByteBuffers from the stream. 16 | *

17 | *

18 | * Actually, read() serves a readonly view of the original ByteBuffer from the stream. 19 | * The original ByteBuffer is not modified, therefore can be shared by multiple SimpleByteSources. 20 | *

21 | */ 22 | public class SimpleByteSource implements ByteSource 23 | { 24 | _StreamIter bbIter; // null if closed 25 | 26 | /** 27 | * Create a SimpleByteSource based on the stream of ByteBuffers. 28 | *

29 | * CAUTION: the stream must be non-blocking in yielding its elements, 30 | * i.e. the tryAdvance() method of 31 | * {@linkplain java.util.stream.Stream#spliterator() its spliterator} 32 | * must be non-blocking. 33 | *

34 | */ 35 | public SimpleByteSource(Stream bbStream) 36 | { 37 | this.bbIter = new _StreamIter<>(bbStream); 38 | } 39 | 40 | /** 41 | * Create a SimpleByteSource based on a single ByteBuffer. 42 | *

43 | * Calling this constructor is equivalent to 44 | * `new SimpleByteSource( Stream.of(bb) )`. 45 | *

46 | */ 47 | public SimpleByteSource(ByteBuffer bb) 48 | { 49 | this(Stream.of(bb)); 50 | } 51 | 52 | /** 53 | * Create a SimpleByteSource of the bytes. 54 | *

55 | * Calling this constructor is equivalent to 56 | * `new SimpleByteSource( ByteBuffer.wrap(bytes) )`. 57 | *

58 | */ 59 | public SimpleByteSource(byte[] bytes) 60 | { 61 | this(ByteBuffer.wrap(bytes)); 62 | } 63 | 64 | 65 | /** 66 | * Read the next chunk of bytes. 67 | *

68 | * This method will serve the next ByteBuffer from the stream. 69 | * Actually, a {@linkplain java.nio.ByteBuffer#asReadOnlyBuffer() readonly view} 70 | * is served, so that the original ByteBuffer will not be touched. 71 | *

72 | * @throws IllegalStateException 73 | * if this source is closed. 74 | */ 75 | @Override 76 | public Async read() throws IllegalStateException 77 | { 78 | if(bbIter ==null) 79 | throw new IllegalStateException("closed"); 80 | 81 | ByteBuffer next = bbIter.next(); 82 | if(next!=null) 83 | return Result.success(next.asReadOnlyBuffer()); // return a duplicate; don't touch origin bb 84 | else 85 | return _Util.EOF; 86 | } 87 | 88 | // skip() 89 | // since everything is in memory, we simply return 0 here, let caller read-and-discard. 90 | // we can't skip a lot faster by doing it internally 91 | 92 | /** 93 | * Close this source. 94 | */ 95 | @Override 96 | public Async close() 97 | { 98 | if(bbIter ==null) 99 | return Async.VOID; 100 | 101 | bbIter.close(); 102 | bbIter = null; 103 | return Async.VOID; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/bayou/bytes/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Async byte source/sink. 3 | */ 4 | package bayou.bytes; -------------------------------------------------------------------------------- /src/bayou/file/FileHttpEntity.java: -------------------------------------------------------------------------------- 1 | package bayou.file; 2 | 3 | import bayou.bytes.ByteSource; 4 | import bayou.http.HttpEntity; 5 | import bayou.mime.ContentType; 6 | import bayou.mime.FileSuffixToContentType; 7 | 8 | import java.nio.file.*; 9 | import java.nio.file.attribute.BasicFileAttributes; 10 | import java.time.Instant; 11 | 12 | /** 13 | * An HttpEntity based on a file. 14 | *

15 | * The body of the entity is the content of the file. 16 | *

17 | *

18 | * This is a sharable entity. 19 | *

20 | */ 21 | public class FileHttpEntity implements HttpEntity 22 | { 23 | final Path filePath; 24 | final FileByteSource.ChannelProvider fileSCP; 25 | // multiple concurrent body readers will share the same file channel 26 | 27 | final Long fileSize; 28 | final Instant lastModified; 29 | 30 | final ContentType contentType; // null is legal. 31 | 32 | // missing properties: expires and contentEncoding. 33 | // user can subclass to provide these properties. 34 | // contentEncoding: 35 | // say the file is abc.html.gz, then Content-Type: text/html, Content-Encoding: gzip 36 | 37 | // constructors may block. tho usually not for very long. (user may take the risk treating it as if non-blocking) 38 | // use AsyncJ8.execAsync(()->new FileEntity()) for non-blocking. 39 | // blocking constructors are useful in blocking context, e.g. during app start up. 40 | 41 | /** 42 | * Create an FileHttpEntity over the file specified by `filePath`. 43 | *

44 | * If contentType==null, we'll use 45 | * {@link bayou.mime.FileSuffixToContentType#getGlobalInstance()} 46 | * to get the content type from the file suffix. 47 | *

48 | *

49 | * CAUTION: this constructor reads file metadata from OS, which may involve blocking IO operations. 50 | *

51 | */ 52 | public FileHttpEntity(Path filePath, ContentType contentType) throws Exception 53 | { 54 | if(contentType==null) 55 | contentType = FileSuffixToContentType.getGlobalInstance().find(filePath.toString()); 56 | 57 | this.filePath = filePath; 58 | this.fileSCP = FileByteSource.ChannelProvider.pooled(filePath); 59 | 60 | this.contentType = contentType; 61 | 62 | // following actions may spin the disk and block 63 | 64 | // we want to find as many problems as we can here, instead of later when body is accessed 65 | BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class); //follows sym link 66 | // line above throws NoSuchFileException if filePath doesn't exist 67 | if(!attrs.isRegularFile()) 68 | throw new NoSuchFileException(filePath.toString()); 69 | 70 | // it would be nice if we check readability here. but it's kind of slow, having visible impact on throughput. 71 | // usually file is readable anyway - app checks first if user is allowed to access the file, 72 | // based on dir etc. if allowed, app should have configured the file to be readable. 73 | // if the file is indeed unreadable, error occur when body is read, client will get no response. 74 | if(false) 75 | { 76 | if(!Files.isReadable(filePath)) // follows sym link 77 | throw new AccessDeniedException(filePath.toString(), null, "file is not readable"); 78 | } 79 | 80 | fileSize = attrs.size(); 81 | 82 | lastModified = attrs.lastModifiedTime().toInstant(); 83 | } 84 | 85 | /** 86 | * The body of the entity, same as the content of the file. 87 | */ 88 | @Override 89 | public ByteSource body() 90 | { 91 | return new FileByteSource(fileSCP); 92 | } 93 | 94 | /** 95 | * The content type. 96 | */ 97 | @Override 98 | public ContentType contentType() { return contentType; } 99 | 100 | /** 101 | * Return the size of the file. 102 | */ 103 | @Override 104 | public Long contentLength() { return fileSize; } 105 | 106 | /** 107 | * Return the last modified time of the file. 108 | */ 109 | @Override 110 | public Instant lastModified() { return lastModified; } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/bayou/file/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities for files. 3 | */ 4 | package bayou.file; -------------------------------------------------------------------------------- /src/bayou/form/CsrfException.java: -------------------------------------------------------------------------------- 1 | package bayou.form; 2 | 3 | /** 4 | * To indicate that an HTTP POST request might be CSRF. 5 | *

6 | * This exception is used when a FormParser cannot definitely prove that 7 | * a request is non-CSRF. 8 | *

9 | *

Generate a response for a CsrfException

10 | *

11 | * Always assume that the client is innocent 12 | * when generating an error response for a CsrfException 13 | * (at the risk of being too nice to a real attacker). 14 | *

15 | *

16 | * Possible reasons for the CsrfException (assuming the client is innocent): 17 | *

18 | *
    19 | *
  • 20 | * If the application does *not* use {@link CsrfToken} in the form,
    21 | * it's likely that the client hides/obfuscates Referer headers for privacy reasons. 22 | *
  • 23 | *
  • 24 | * If the application *does* use {@link CsrfToken} in the form,
    25 | * it's likely that the client disables cookies *and* hides/obfuscates Referer headers. 26 | *
  • 27 | *
28 | */ 29 | public class CsrfException extends Exception 30 | { 31 | public CsrfException(String msg) 32 | { 33 | super(msg); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/bayou/form/DoGenUrlEncoded.java: -------------------------------------------------------------------------------- 1 | package bayou.form; 2 | 3 | import _bayou._str._CharDef; 4 | import _bayou._str._HexUtil; 5 | 6 | import java.nio.charset.Charset; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | class DoGenUrlEncoded 12 | { 13 | byte[] bytes = new byte[64]; 14 | int ib; 15 | 16 | void append(int c) 17 | { 18 | if(ib==bytes.length) 19 | bytes = Arrays.copyOf(bytes, ib*3/2); 20 | bytes[ib++] = (byte)c; 21 | } 22 | 23 | DoGenUrlEncoded(Charset charset, Map> parameters, Map> files) 24 | { 25 | for(Map.Entry> x : parameters.entrySet()) 26 | { 27 | String name = x.getKey(); 28 | byte[] nameBytes = name.getBytes(charset); 29 | for(String value : x.getValue()) 30 | addPair(nameBytes, value, charset); 31 | } 32 | 33 | // files: only fileName is encoded 34 | for(Map.Entry> x : files.entrySet()) 35 | { 36 | String name = x.getKey(); 37 | byte[] nameBytes = name.getBytes(charset); 38 | for(FormDataFile fdf : x.getValue()) 39 | addPair(nameBytes, fdf.fileName, charset); 40 | } 41 | } 42 | 43 | void addPair(byte[] nameBytes, String value, Charset charset) 44 | { 45 | byte[] valueBytes = value.getBytes(charset); 46 | if(ib!=0) 47 | append('&'); 48 | escape(nameBytes); 49 | append('='); 50 | escape(valueBytes); 51 | } 52 | 53 | void escape(byte[] bytes) 54 | { 55 | for(byte b : bytes) 56 | { 57 | int u = 0xff & b; 58 | if(_CharDef.check(u, _CharDef.Html.safeQueryChars)) 59 | append( u ); 60 | else if(u==' ') 61 | append('+'); 62 | else 63 | { 64 | append('%'); 65 | append(_HexUtil.int2hex[ u >> 4 ]); 66 | append(_HexUtil.int2hex[ u & 0x0f ]); 67 | } 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/bayou/form/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Html form handling. 3 | */ 4 | package bayou.form; -------------------------------------------------------------------------------- /src/bayou/gzip/GzipHeaderParser.java: -------------------------------------------------------------------------------- 1 | package bayou.gzip; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.zip.CRC32; 5 | import java.util.zip.ZipException; 6 | 7 | // http://tools.ietf.org/html/rfc1952#page-5 8 | class GzipHeaderParser 9 | { 10 | enum State{ head, extra1, extra2, name, comment, crc, done } 11 | 12 | static State jump(State state, int flag) 13 | { 14 | switch(state) 15 | { 16 | case head : if( (flag&(1<<2))!=0 ) return State.extra1; 17 | case extra2 : if( (flag&(1<<3))!=0 ) return State.name; 18 | case name : if( (flag&(1<<4))!=0 ) return State.comment; 19 | case comment : if( (flag&(1<<1))!=0 ) return State.crc; 20 | case crc : /* */ return State.done; 21 | } 22 | throw new AssertionError(); // should not reach here 23 | } 24 | 25 | State state = State.head; 26 | boolean done(){ return state==State.done; } 27 | 28 | byte[] buf = new byte[10]; 29 | int x; 30 | int flag; 31 | CRC32 crc; 32 | 33 | 34 | 35 | void parse(ByteBuffer bb) throws ZipException 36 | { 37 | while(state!=State.done && bb.hasRemaining()) 38 | { 39 | byte b = bb.get(); 40 | if(crc!=null && state!=State.crc) 41 | crc.update(b); 42 | 43 | switch(state) 44 | { 45 | case head: // collect 10 bytes 46 | buf[x++]=b; 47 | if(x<10) break; 48 | checkHead(); // throws 49 | x=0; 50 | state = jump(state, flag); 51 | break; 52 | 53 | case extra1: // collect 2 bytes 54 | buf[x++]=b; 55 | if(x<2) break; 56 | x = 256*(0xFF & buf[1]) + (0xFF & buf[0]); // x=XLEN 57 | state = State.extra2; 58 | if(x==0) 59 | state = jump(state, flag); 60 | break; 61 | 62 | case extra2: // collect XLEN bytes 63 | --x; 64 | if(x>0) break; 65 | state = jump(state, flag); 66 | break; 67 | 68 | // zero-terminated. we don't impose length limit. 69 | case name: 70 | case comment: 71 | if(b!=0) break; 72 | state = jump(state, flag); 73 | break; 74 | 75 | case crc: // collect 2 bytes 76 | buf[x++]=b; 77 | if(x<2) break; 78 | checkCrc(); // throws 79 | state=State.done; 80 | break; 81 | 82 | default: 83 | throw new AssertionError(); 84 | } 85 | } 86 | } 87 | 88 | void checkHead() throws ZipException 89 | { 90 | if(buf[0]!=(byte) 31) throw new ZipException("Invalid ID1: "+buf[0]); 91 | if(buf[1]!=(byte)139) throw new ZipException("Invalid ID2: "+buf[1]); 92 | if(buf[2]!=(byte) 8) throw new ZipException("Invalid CM: " +buf[2]); 93 | 94 | flag = 0xFF & buf[3]; 95 | if( (flag&0B1110_0000)!=0 ) throw new ZipException("reserved bits set in FLG: "+flag); 96 | 97 | if( (flag&(1<<1))!=0 ) 98 | { 99 | crc = new CRC32(); 100 | crc.update(buf, 0, 10); 101 | } 102 | } 103 | 104 | void checkCrc() throws ZipException 105 | { 106 | long v = crc.getValue(); 107 | byte v0 = (byte)(v ); 108 | byte v1 = (byte)(v>>8); 109 | if(v0!=buf[0] || v1!=buf[1]) throw new ZipException("Invalid CRC16"); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/bayou/gzip/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * GZIP related utils. 3 | */ 4 | package bayou.gzip; 5 | 6 | // this deserves a separate package, since we'll probably add more gzip/gunzip stuff in future. -------------------------------------------------------------------------------- /src/bayou/html/HtmlChildList.java: -------------------------------------------------------------------------------- 1 | package bayou.html; 2 | 3 | import java.util.AbstractList; 4 | 5 | class HtmlChildList extends AbstractList 6 | { 7 | final HtmlParent parent; 8 | 9 | HtmlChildList(HtmlParent parent) 10 | { 11 | this.parent = parent; 12 | } 13 | 14 | @Override 15 | public HtmlPiece get(int index) 16 | { 17 | return parent.getChild(index); 18 | } 19 | 20 | @Override 21 | public int size() 22 | { 23 | return parent.getChildCount(); 24 | } 25 | 26 | @Override 27 | public HtmlPiece set(int index, HtmlPiece element) 28 | { 29 | return parent.setChild(index, element); 30 | } 31 | 32 | @Override 33 | public void add(int index, HtmlPiece element) 34 | { 35 | parent.addChild(index, element); 36 | } 37 | 38 | @Override 39 | public HtmlPiece remove(int index) 40 | { 41 | return parent.removeChild(index); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/bayou/html/HtmlComment.java: -------------------------------------------------------------------------------- 1 | package bayou.html; 2 | 3 | import _bayou._tmp._Array2ReadOnlyList; 4 | import _bayou._str._CharSubSeq; 5 | 6 | import java.util.List; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Html comment. 11 | *

12 | * For example, 13 | * `new HtmlComment("main article")` will be rendered as 14 | * "<!-- main article -->". 15 | *

16 | *

17 | * Escaping: 18 | * consecutive dashes "--" are not allowed in html comments. 19 | * We escape it by inserting a backslash. For example, 20 | * `new HtmlComment("xx--yy\\zz")` will be rendered as 21 | * "<!-- xx-\-yy\\zz -->". 22 | * Note that the backslash is also escaped to "\\". 23 | *

24 | */ 25 | // we expect comment to be small. 26 | // mostly used by dev to match source code (java) and target code (html). 27 | public class HtmlComment implements HtmlPiece 28 | { 29 | final Object[] content; 30 | 31 | /** 32 | * Create an HtmlComment. 33 | *

34 | * See {@link #getContent()}. 35 | *

36 | *

37 | * If an `obj` in `content` is not a CharSequence, it will be converted to one by 38 | * `String.valueOf(obj)`. `obj` can be `null`. 39 | *

40 | */ 41 | public HtmlComment(Object... content) 42 | { 43 | this.content = HtmlHelper.toCharSeq(content); 44 | } 45 | 46 | /** 47 | * Get the content of this piece. 48 | *

49 | * The returned char sequences are from `content` passed to the constructor. 50 | *

51 | *

52 | * No escaping is done by this method. 53 | * If you want an escaped result, use `render(0)`. 54 | *

55 | */ 56 | // can be used in unit test 57 | public List getContent() 58 | { 59 | return new _Array2ReadOnlyList<>(content); 60 | } 61 | 62 | 63 | // will be properly indented between two blocks 64 | // 65 | // 66 | //
67 | 68 | /** 69 | * Render this piece as an html comment "<!-- ... -->". 70 | *

71 | * Escaping is done by this method. 72 | *

73 | */ 74 | @Override 75 | public void render(int indent, Consumer out) 76 | { 77 | out.accept(""); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/bayou/html/HtmlDoc.java: -------------------------------------------------------------------------------- 1 | package bayou.html; 2 | 3 | import bayou.http.HttpResponseImpl; 4 | import bayou.http.HttpStatus; 5 | import bayou.mime.ContentType; 6 | import bayou.text.TextDoc; 7 | import bayou.text.TextHttpEntity; 8 | 9 | import java.nio.charset.Charset; 10 | import java.nio.charset.StandardCharsets; 11 | 12 | /** 13 | * Html document. 14 | */ 15 | public interface HtmlDoc extends TextDoc 16 | { 17 | 18 | /** 19 | * Get the content type of this document, by default, "text/html;charset=UTF-8". 20 | *

21 | * If {@link #getCharset()} is overridden (and this method is not), 22 | * this method will return a content type with the charset parameter provided by {@link #getCharset()}. 23 | *

24 | */ 25 | @Override 26 | default public ContentType getContentType() 27 | { 28 | Charset charset = getCharset(); 29 | if(charset== StandardCharsets.UTF_8) 30 | return ContentType.text_html_UTF_8; 31 | else // subclass overrides getCharset() but not getContentType() 32 | return new ContentType("text", "html", "charset", charset.name()); 33 | } 34 | 35 | /** 36 | * Get the charset of this document, by default "UTF-8". 37 | */ 38 | @Override 39 | default public Charset getCharset() 40 | { 41 | return StandardCharsets.UTF_8; 42 | } 43 | 44 | /** 45 | * Create an http response serving this document. 46 | */ 47 | default HttpResponseImpl toResponse(int statusCode) 48 | { 49 | return new HttpResponseImpl(HttpStatus.of(statusCode), new TextHttpEntity(this)); 50 | } 51 | // maybe move this method to TextDoc. later. 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/bayou/html/HtmlElementType.java: -------------------------------------------------------------------------------- 1 | package bayou.html; 2 | 3 | /** 4 | * Metadata of an html element, including its name etc. 5 | *

6 | * See {@link HtmlElement#getElementType()}. 7 | *

8 | *

9 | * The properties of this object are mainly used in rendering. 10 | *

11 | */ 12 | public class HtmlElementType 13 | { 14 | /** 15 | * The name of this element, for example "div". 16 | */ 17 | public final String name; 18 | 19 | final String startTag; // "" 20 | final String startTagX; // "", null if no end tag. 22 | 23 | /** 24 | * Whether this is a "void" element, one that cannot contain children. 25 | *

26 | * For example, <img> is a void element. 27 | *

28 | *

29 | * A void element must not have an end tag. 30 | * Don't use self-closing (e.g. <img ... />) either. 31 | *

32 | */ 33 | public final boolean isVoid; 34 | 35 | /** 36 | * Whether this is a block element. 37 | *

38 | * For example, <div> is a block element. 39 | *

40 | *

41 | * It is safe to introduce spaces around block elements. 42 | * See {@link HtmlPiece#isBlock()}. 43 | *

44 | *

45 | * An element is either a block or an inline element. 46 | *

47 | */ 48 | public final boolean isBlock; 49 | 50 | // block-ish element, but spaces inside are important. 51 | /** 52 | * Whether spaces should be preserved inside this element. 53 | *

54 | * For example, <pre> <script> <style> <textarea> 55 | * are "pre" elements. 56 | *

57 | */ 58 | public final boolean isPre; // pre script style textarea. 59 | // textarea is both inline and pre. 60 | 61 | /** 62 | * Create an HtmlElementType. 63 | *

64 | * Example usage: {@code new HtmlElementType("div", false, true, false)} 65 | *

66 | */ 67 | public HtmlElementType(String name, boolean isVoid, boolean isBlock, boolean isPre) 68 | { 69 | this.name = name; // we don't check name. it's complicated. hopefully name is valid. 70 | this.isVoid = isVoid; 71 | this.isBlock = isBlock; 72 | this.isPre = isPre; 73 | 74 | this.startTag = "<"+name+">"; 75 | this.startTagX = "<"+name+" "; 76 | this.endTag = isVoid? null : ""; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/bayou/html/HtmlHelper.java: -------------------------------------------------------------------------------- 1 | package bayou.html; 2 | 3 | import _bayou._str._CharSubSeq; 4 | 5 | import java.util.function.Consumer; 6 | 7 | class HtmlHelper 8 | { 9 | // customization of indentation. 10 | // for now, do not publicize this feature. indentation overhead is small. 11 | 12 | // default is TAB, which is fine - most modern editors will show 4 spaces for 1 TAB. 13 | static final String INDENT = System.getProperty(HtmlPiece.class.getName()+".INDENT", "\t"); 14 | // if INDENT="", we still print new lines 15 | 16 | static final boolean disabled = INDENT.equalsIgnoreCase("disabled"); 17 | 18 | static String x_indent(int level) 19 | { 20 | if(disabled) 21 | return ""; 22 | String s = "\n"; 23 | for(int i=0; i out) 66 | { 67 | int L = csq.length(); 68 | int s=0; 69 | for(int i=0; is) 76 | out.accept(_CharSubSeq.of(csq, s, i)); 77 | out.accept(esc); 78 | s=i+1; 79 | } 80 | } 81 | if(L>s) 82 | { 83 | if(s==0) 84 | out.accept(csq); 85 | else 86 | out.accept(_CharSubSeq.of(csq, s, L)); 87 | } 88 | } 89 | 90 | // length=128 to help branch prediction on c'] = ">"; 96 | escMap3['&'] = "&"; 97 | } 98 | static void renderEscaped3(CharSequence csq, Consumer out) 99 | { 100 | renderEscaped(escMap3, csq, out); 101 | } 102 | 103 | static final String[] escMap4 = new String[128]; 104 | static 105 | { 106 | escMap4['<'] = "<"; 107 | escMap4['>'] = ">"; 108 | escMap4['&'] = "&"; 109 | escMap4['"'] = """; 110 | } 111 | static void renderEscaped4(CharSequence csq, Consumer out) 112 | { 113 | renderEscaped(escMap4, csq, out); 114 | } 115 | 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/bayou/html/HtmlNewLine.java: -------------------------------------------------------------------------------- 1 | package bayou.html; 2 | 3 | import java.util.function.Consumer; 4 | 5 | /** 6 | * A new line ("\n") in html. 7 | *

8 | * See the {@link #render(int, java.util.function.Consumer) render(indent, out)} method in this class 9 | * for how it's actually rendered. 10 | *

11 | *

12 | * The rendering algorithm will not automatically introduce spaces between two inline elements. 13 | * You can manually insert an HtmlNewLine between two inline elements for formatting purpose 14 | * (if it's ok to insert spaces between them). 15 | *

16 | *

17 | * See also {@link HtmlBuilder#_newline()}. 18 | *

19 | */ 20 | public final class HtmlNewLine implements HtmlPiece 21 | { 22 | /** 23 | * An instance of HtmlNewLine. 24 | * Note that HtmlNewLine is stateless. 25 | */ 26 | public static final HtmlNewLine instance = new HtmlNewLine(); 27 | 28 | // we don't care to make this class a singleton class. 29 | // users can create new instances if they want to. 30 | 31 | /** 32 | * Create an HtmlNewLine instance. 33 | *

34 | * Note that HtmlNewLine is stateless. You can use 35 | * {@link HtmlNewLine#instance HtmlNewLine.instance} instead. 36 | *

37 | */ 38 | public HtmlNewLine() 39 | { 40 | 41 | } 42 | 43 | /** 44 | * Render a new line to `out`. 45 | *

46 | * More accurately, render an indentation string 47 | * {@link HtmlPiece#indent(int) HtmlPiece.indent(indent)} to `out`. 48 | *

49 | *

50 | * Note that if `indent<0`, nothing will be rendered. 51 | * If necessary, use {@link HtmlRaw}("\n") instead. 52 | *

53 | */ 54 | @Override 55 | public void render(int indent, Consumer out) 56 | { 57 | out.accept(HtmlPiece.indent(indent)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/bayou/html/HtmlRaw.java: -------------------------------------------------------------------------------- 1 | package bayou.html; 2 | 3 | import _bayou._tmp._Array2ReadOnlyList; 4 | 5 | import java.util.List; 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * Html raw content. 10 | *

11 | * The content will be rendered verbatim, without any escaping. 12 | *

13 | *

14 | * For example, 15 | * `new HtmlRaw("<td>", 1, "</td>")` will be rendered as 16 | * "<td>1</td>". 17 | *

18 | * 19 | */ 20 | // can be used for things like doc type, copy&paste html snippet, etc. 21 | // note: raw chars, not raw bytes. 22 | public class HtmlRaw implements HtmlPiece 23 | { 24 | final Object[] content; 25 | 26 | /** 27 | * Create an HtmlRaw. 28 | *

29 | * See {@link #getContent()}. 30 | *

31 | *

32 | * If an `obj` in `content` is not a CharSequence, it will be converted to one by 33 | * `String.valueOf(obj)`. `obj` can be `null`. 34 | *

35 | */ 36 | public HtmlRaw(Object... content) 37 | { 38 | this.content = HtmlHelper.toCharSeq(content); 39 | } 40 | 41 | /** 42 | * Get the content of this piece. 43 | *

44 | * The returned char sequences are from `content` passed to the constructor. 45 | *

46 | */ 47 | // can be used in unit test 48 | public List getContent() 49 | { 50 | return new _Array2ReadOnlyList<>(content); 51 | } 52 | 53 | /** 54 | * Write the content to `out`, without any escaping. 55 | */ 56 | @Override 57 | public void render(int indent, Consumer out) 58 | { 59 | for(Object csq : content) 60 | out.accept((CharSequence)csq); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/bayou/html/HtmlText.java: -------------------------------------------------------------------------------- 1 | package bayou.html; 2 | 3 | import _bayou._tmp._Array2ReadOnlyList; 4 | 5 | import java.util.List; 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * Html textual content. 10 | *

11 | * For example, 12 | * `new HtmlText("n>", 1)` will be rendered as 13 | * "n&gt;1". 14 | *

15 | *

16 | * Escaping: 17 | * The following chars will be escaped: 18 | *

19 | *
    20 | *
  • <  →  &lt;
  • 21 | *
  • >  →  &gt;
  • 22 | *
  • &  →  &amp;
  • 23 | *
24 | *

25 | * Note that double/single quotes are not escaped. 26 | *

27 | */ 28 | public class HtmlText implements HtmlPiece 29 | { 30 | // double-quote is not escaped. 31 | // it's quite common in texts. we don't want the performance penalty of escaping it. 32 | // and it would look ugly when view source. 33 | 34 | 35 | final Object[] content; 36 | 37 | /** 38 | * Create an HtmlText piece. 39 | *

40 | * See {@link #getContent()}. 41 | *

42 | *

43 | * If an `obj` in `content` is not a CharSequence, it will be converted to one by 44 | * `String.valueOf(obj)`. `obj` can be `null`. 45 | *

46 | */ 47 | public HtmlText(Object... content) 48 | { 49 | this.content = HtmlHelper.toCharSeq(content); 50 | } 51 | // we also tried to optimize for single-arg case, it doesn't help. 52 | 53 | /** 54 | * Get the content of this piece. 55 | *

56 | * The returned char sequences are from `content` passed to the constructor. 57 | *

58 | *

59 | * No escaping is done by this method. 60 | * If you want an escaped result, use `render(0)`. 61 | *

62 | */ 63 | // can be used in unit test 64 | public List getContent() 65 | { 66 | return new _Array2ReadOnlyList<>(content); 67 | } 68 | 69 | /** 70 | * Write the content to `out`, escaping special chars. 71 | */ 72 | @Override 73 | public void render(int indent, Consumer out) 74 | { 75 | for(Object csq : content) 76 | HtmlHelper.renderEscaped3((CharSequence) csq, out); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/bayou/html/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * For building html trees. 3 | */ 4 | package bayou.html; -------------------------------------------------------------------------------- /src/bayou/http/ConnectTunnel.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import bayou.async.Async; 4 | import bayou.tcp.TcpAddress; 5 | import bayou.tcp.TcpConnection; 6 | import bayou.tcp.TcpTunnel; 7 | import bayou.util.UserPass; 8 | 9 | import java.util.HashMap; 10 | import java.util.function.Function; 11 | import java.util.function.Supplier; 12 | 13 | /** 14 | * A TcpTunnel using HTTP CONNECT method. 15 | * See RFC7231 §4.3.6 16 | *

17 | * Such tunnel is often used for "HTTPS proxy"; but it can be used for tunneling any TCP traffic too. 18 | *

19 | *

20 | * Basic/Digest authentications are supported. 21 | *

22 | *

23 | * This class is stateful for caching authentication state. 24 | *

25 | */ 26 | public class ConnectTunnel implements TcpTunnel 27 | { 28 | TcpAddress address; // can be ssl 29 | 30 | // null if auth not supported 31 | Function> userPassFunc; 32 | HashMap auth_cache; 33 | 34 | /** 35 | * Create an HTTP CONNECT tunnel. 36 | *

37 | * If userPassSupplier!=null, BASIC and DIGEST authentications are supported. 38 | *

39 | */ 40 | public ConnectTunnel(TcpAddress address, Supplier> userPassSupplier) 41 | { 42 | this.address = address; 43 | 44 | if(userPassSupplier!=null) 45 | { 46 | userPassFunc = addr->userPassSupplier.get(); 47 | auth_cache = new HashMap<>(); 48 | } 49 | } 50 | 51 | /** 52 | * Create an HTTP CONNECT tunnel. 53 | *

54 | * This is a convenience method for 55 | * {@link #ConnectTunnel(bayou.tcp.TcpAddress, java.util.function.Supplier) 56 | * ConnectTunnel(address, userPassSupplier)} 57 | * with address.ssl=false. 58 | *

59 | */ 60 | public ConnectTunnel(String host, int port, String username, String password) 61 | { 62 | this( 63 | new TcpAddress(false, host, port), 64 | ()->Async.success(new UserPass(username, password)) 65 | ); 66 | } 67 | 68 | /** 69 | * Create an HTTP CONNECT tunnel. 70 | *

71 | * This is a convenience method for 72 | * {@link #ConnectTunnel(bayou.tcp.TcpAddress, java.util.function.Supplier) 73 | * ConnectTunnel(address, userPassSupplier)} 74 | * with address.ssl=false, userPassSupplier=null. 75 | *

76 | */ 77 | public ConnectTunnel(String host, int port) 78 | { 79 | this( new TcpAddress(false, host, port), null); 80 | } 81 | 82 | @Override 83 | public TcpAddress address() 84 | { 85 | return address; 86 | } 87 | 88 | 89 | 90 | 91 | 92 | @Override 93 | public Async tunnelTo(TcpConnection tcpConn, String host, int port) 94 | { 95 | // send CONNECT request to next hop 96 | // may handle one round of authentication 97 | 98 | HttpClientConnection hConn = new HttpClientConnection(tcpConn); 99 | // [close on exception] 100 | 101 | HttpHandler handler = request->hConn.send(request).then(v->hConn.receive()); 102 | 103 | if(userPassFunc!=null) 104 | handler = new AuthHandler(address, handler, userPassFunc, auth_cache); 105 | 106 | // request target. if host is ipv6 literal, it must be enclosed with [] 107 | // target = host:port 108 | // host = domain / ipv4 / "[" ipv6 "]" 109 | String nextHost_Port = (host.indexOf(':')==-1) ? host+":"+port 110 | : "[" + host + "]:" + port; 111 | HttpRequestImpl req = new HttpRequestImpl("CONNECT", nextHost_Port, null); 112 | 113 | return handler.handle(req) 114 | .then(response-> 115 | { 116 | if(response.statusCode()/100==2) // 2xx means success. there's no response body. 117 | return Async.success(tcpConn); 118 | return Async.failure(new Exception("failed to tunnel to "+nextHost_Port+", " + 119 | "response.status="+response.status())); 120 | }) 121 | .catch__(Exception.class, ex -> { 122 | hConn.close(); 123 | throw ex; 124 | }); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/bayou/http/CookieHandler.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import bayou.async.Async; 4 | import bayou.mime.Headers; 5 | import bayou.util.Result; 6 | 7 | import java.util.List; 8 | 9 | 10 | // issue: 1xx responses won't reach this handler; their cookies will not be handled. 11 | 12 | class CookieHandler implements HttpHandler 13 | { 14 | HttpHandler h0; 15 | CookieStorage store; 16 | 17 | CookieHandler(HttpHandler h0, CookieStorage store) 18 | { 19 | this.h0 = h0; 20 | this.store = store; 21 | } 22 | 23 | @Override 24 | public Async handle(HttpRequest request) 25 | { 26 | Async cookieStringA = getCookieString(store, request); 27 | Result cookieStringR = cookieStringA.pollResult(); 28 | if(cookieStringR!=null) // common 29 | return handle(request, cookieStringR); 30 | return cookieStringA.transform(r->handle(request, r)); 31 | } 32 | 33 | Async handle(HttpRequest request, Result cookieStringR) 34 | { 35 | String cookieString = cookieStringR.getValue(); 36 | if(cookieString==null) // failure. unlikely. 37 | return cookieStringR.covary(); // erasure 38 | 39 | if(!cookieString.isEmpty()) // otherwise no cookie from store 40 | { 41 | String old = request.header(Headers.Cookie); 42 | if(old!=null) // app already set some cookies. keep them. 43 | cookieString = old + ";" +cookieString; 44 | request = new HttpRequestImpl(request).header(Headers.Cookie, cookieString); 45 | } 46 | 47 | HttpRequest requestF = request; 48 | return h0 49 | .handle(requestF) 50 | .transform(r->onResponse(r, requestF)) 51 | ; 52 | } 53 | Async onResponse(Result responseR, HttpRequest request) 54 | { 55 | HttpResponse response = responseR.getValue(); 56 | if(response==null) // failure 57 | return responseR; 58 | 59 | Async v = store.setCookies(request, response); 60 | if(v.isCompleted()) // common 61 | return responseR; 62 | // otherwise wait for its completion 63 | return v.transform(r->responseR); // don't care if `r` is failure. 64 | } 65 | 66 | 67 | ////////////////////////////////////////////////////////////////////////////////////////////////////// 68 | // result is "" if there's no cookies 69 | static Async getCookieString(CookieStorage store, HttpRequest request) 70 | { 71 | Async> listA = store.getCookies(request); 72 | Result> listR = listA.pollResult(); 73 | if(listR!=null) // common 74 | return toCookieString2(listR); 75 | return listA.transform(CookieHandler::toCookieString2); 76 | } 77 | static Async toCookieString2(Result> listR) 78 | { 79 | List list = listR.getValue(); 80 | if(list==null) // failure 81 | return listR.covary(); // erasure 82 | else 83 | return Result.success(toCookieString3(list)); 84 | } 85 | static String toCookieString3(List list) 86 | { 87 | if(list.isEmpty()) 88 | return ""; 89 | 90 | StringBuilder sb = new StringBuilder(); 91 | for(int i=0;i0) sb.append(';'); 94 | Cookie c = list.get(i); 95 | sb.append(c.name()).append('=').append(c.value()); 96 | } 97 | return sb.toString(); 98 | } 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/bayou/http/CookieStorage.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import bayou.async.Async; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Cookie storage for HttpClient. See {@link HttpClientConf#cookieStorage(CookieStorage)}. 9 | *

10 | * Before sending a request, HttpClient calls {@link #getCookies(HttpRequest)}; 11 | * after receiving the response, HttpClient calls {@link #setCookies(HttpRequest, HttpResponse)}. 12 | *

13 | */ 14 | public interface CookieStorage 15 | { 16 | /** 17 | * Get the cookies that should be sent with the request. 18 | */ 19 | public Async> getCookies(HttpRequest request); 20 | // no consideration for HttpOnly. (HttpOnly is retarded anyway) 21 | // if necessary, caller can filter the result cookies. 22 | 23 | /** 24 | * Save the cookies carried in the response. 25 | */ 26 | public Async setCookies(HttpRequest request, HttpResponse response); 27 | // atomicity: ideally we should atomically set all cookies in a response, 28 | // because there could be integrity across multiple cookies. 29 | // this is not a strict requirement though. 30 | // causality: after this action completes, getCookies() should see the effects. 31 | 32 | 33 | /** 34 | * Create a new instance of an in-memory implementation of CookieStorage. 35 | */ 36 | public static CookieStorage newInMemoryStorage() 37 | { 38 | return new InMemoryCookieStorage(); 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/bayou/http/HttpAccessLoggerWrapper.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import _bayou._tmp._Exec; 4 | 5 | import java.util.concurrent.RejectedExecutionException; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.function.Consumer; 8 | 9 | // for concurrent access 10 | class HttpAccessLoggerWrapper 11 | { 12 | final Consumer printer; 13 | 14 | // 1 thread, not daemon, expires after 10sec. unbounded queue. tasks are serialized 15 | final ThreadPoolExecutor serialExec; 16 | 17 | public HttpAccessLoggerWrapper(Consumer printer) 18 | { 19 | this.printer = printer; 20 | 21 | serialExec = _Exec.newSerialExecutor("Http Access Logger"); 22 | } 23 | 24 | public void print(HttpAccess entry) 25 | { 26 | Runnable task = () -> 27 | { 28 | try 29 | { 30 | printer.accept(entry); 31 | } 32 | catch (Error | RuntimeException e ) // printer is broken 33 | { 34 | serialExec.shutdownNow(); 35 | 36 | System.err.println("Bayou HttpAccessLog printer is broken: "+e); 37 | e.printStackTrace(); // dump to console instead of to logger 38 | } 39 | }; 40 | 41 | try 42 | { 43 | serialExec.execute(task); 44 | } 45 | catch (RejectedExecutionException e) 46 | { 47 | // ok. printer was broken. new logs are ignored. 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/bayou/http/HttpEntityMod.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import bayou.bytes.ByteSource; 4 | import bayou.mime.ContentType; 5 | 6 | import java.time.Instant; 7 | 8 | // a wrapper of an origin entity, where some metadata can be overridden 9 | class HttpEntityMod implements HttpEntity 10 | { 11 | // sentinel values for fields not overridden 12 | 13 | static final String originString = new String(new char[0]); 14 | 15 | static final Instant originInstant = Instant.ofEpochMilli(0x1d96cf3c234e2664L); 16 | // hopefully this is a unique object. there's no Instant constructor accessible. 17 | 18 | 19 | final HttpEntity origin; 20 | HttpEntityMod(HttpEntity origin) 21 | { 22 | this.origin = origin; 23 | } 24 | 25 | @Override 26 | public ByteSource body() throws IllegalStateException 27 | { 28 | return origin.body(); 29 | } 30 | 31 | @Override 32 | public ContentType contentType() 33 | { 34 | return origin.contentType(); 35 | } 36 | 37 | @Override 38 | public Long contentLength() 39 | { 40 | return origin.contentLength(); 41 | } 42 | 43 | @Override 44 | public String contentEncoding() 45 | { 46 | return origin.contentEncoding(); 47 | } 48 | 49 | 50 | 51 | 52 | Instant lastModified=originInstant; 53 | @Override 54 | public Instant lastModified() 55 | { 56 | if(lastModified==originInstant) 57 | return origin.lastModified(); 58 | else 59 | return this.lastModified; 60 | } 61 | 62 | Instant expires=originInstant; 63 | @Override 64 | public Instant expires() 65 | { 66 | if(expires==originInstant) 67 | return origin.expires(); 68 | else 69 | return this.expires; 70 | } 71 | 72 | String etag=originString; 73 | @Override 74 | public String etag() 75 | { 76 | if(etag==originString) 77 | return origin.etag(); 78 | else 79 | return this.etag; 80 | } 81 | 82 | Boolean etagIsWeak=null; // null means no mod 83 | @Override 84 | public boolean etagIsWeak() 85 | { 86 | if(etagIsWeak==null) 87 | return origin.etagIsWeak(); 88 | else 89 | return etagIsWeak.booleanValue(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/bayou/http/HttpEntityWrapper.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import bayou.bytes.ByteSource; 4 | import bayou.mime.ContentType; 5 | 6 | import java.time.Instant; 7 | 8 | /** 9 | * A wrapper of HttpEntity. 10 | *

11 | * This interface implements HttpEntity methods by forwarding the calls 12 | * to the origin entity. 13 | * A subclass needs to implement {@link #getOriginEntity()}, 14 | * and selectively override some HttpEntity methods. 15 | *

16 | */ 17 | public interface HttpEntityWrapper extends HttpEntity 18 | { 19 | // CAUTION: subclass is holding a reference to origin entity, which could be wasting memory 20 | 21 | /** 22 | * The origin entity. 23 | */ 24 | abstract HttpEntity getOriginEntity(); 25 | 26 | /** 27 | * Equivalent to getOriginEntity().body() by default. 28 | */ 29 | @Override 30 | default ByteSource body() 31 | { 32 | return getOriginEntity().body(); 33 | } 34 | 35 | /** 36 | * Equivalent to getOriginEntity().contentType() by default. 37 | */ 38 | @Override 39 | default ContentType contentType() 40 | { 41 | return getOriginEntity().contentType(); 42 | } 43 | 44 | /** 45 | * Equivalent to getOriginEntity().contentLength() by default. 46 | */ 47 | @Override 48 | default Long contentLength() 49 | { 50 | return getOriginEntity().contentLength(); 51 | } 52 | 53 | /** 54 | * Equivalent to getOriginEntity().contentEncoding() by default. 55 | */ 56 | @Override 57 | default String contentEncoding() 58 | { 59 | return getOriginEntity().contentEncoding(); 60 | } 61 | 62 | /** 63 | * Equivalent to getOriginEntity().lastModified() by default. 64 | */ 65 | @Override 66 | default Instant lastModified() 67 | { 68 | return getOriginEntity().lastModified(); 69 | } 70 | 71 | /** 72 | * Equivalent to getOriginEntity().expires() by default. 73 | */ 74 | @Override 75 | default Instant expires() 76 | { 77 | return getOriginEntity().expires(); 78 | } 79 | 80 | /** 81 | * Equivalent to getOriginEntity().etag() by default. 82 | */ 83 | @Override 84 | default String etag() 85 | { 86 | return getOriginEntity().etag(); 87 | } 88 | 89 | /** 90 | * Equivalent to getOriginEntity().etagIsWeak() by default. 91 | */ 92 | @Override 93 | default boolean etagIsWeak() 94 | { 95 | return getOriginEntity().etagIsWeak(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/bayou/http/HttpHelper.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import _bayou._tmp._Util; 4 | import bayou.async.Async; 5 | import bayou.async.FiberLocal; 6 | import bayou.bytes.ByteSource; 7 | import bayou.mime.ContentType; 8 | import bayou.util.Result; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.nio.charset.StandardCharsets; 12 | 13 | class HttpHelper 14 | { 15 | static final FiberLocal fiberLocalRequest = new FiberLocal<>(); 16 | 17 | 18 | // msg charset: "ISO-8859-1" 19 | static HttpResponseImpl simpleResp(HttpStatus status, String msg) 20 | { 21 | SimpleTextEntity entity = new SimpleTextEntity(msg); 22 | return new HttpResponseImpl(status, entity); 23 | } 24 | 25 | // return an id for the error; id can be sent to client for diagnosis purpose. 26 | public static String logErrorWithId(Throwable error) 27 | { 28 | String id = _Util.msgRef(error.toString()); 29 | HttpServer.logger.error("[error id: %s] %s", id, error); 30 | return id; 31 | } 32 | 33 | static class SimpleTextEntity implements HttpEntity 34 | { 35 | byte[] msg; 36 | SimpleTextEntity(String msg) 37 | { 38 | this.msg = msg.getBytes(StandardCharsets.ISO_8859_1); 39 | } 40 | @Override public ContentType contentType() 41 | { 42 | return ContentType.text_plain_ISO88591; 43 | } 44 | @Override public Long contentLength() 45 | { 46 | return (long)msg.length; 47 | } 48 | @Override public ByteSource body() 49 | { 50 | return new ByteSource() 51 | { 52 | boolean end; 53 | @Override public Async read() 54 | { 55 | if(end) 56 | return _Util.EOF; 57 | end =true; 58 | return Result.success(ByteBuffer.wrap(msg)); 59 | } 60 | @Override public Async close(){ return Async.VOID; } 61 | }; 62 | } 63 | } 64 | 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/bayou/http/HttpProxy.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import _bayou._tmp._Util; 4 | import bayou.async.Async; 5 | import bayou.tcp.TcpAddress; 6 | import bayou.util.Result; 7 | import bayou.util.UserPass; 8 | 9 | import java.util.function.Supplier; 10 | 11 | /** 12 | * Proxy information for HttpClient. See {@link HttpClientConf#proxy(HttpProxy)}. 13 | *

14 | * This class is a simple data structure for proxy address and authentication. 15 | *

16 | * 17 | */ 18 | public class HttpProxy 19 | { 20 | private TcpAddress address; 21 | 22 | private Supplier> userPassSupplier; 23 | 24 | /** 25 | * Create an HttpProxy. 26 | *

27 | * If userPassSupplier!=null, Basic/Digest authentications 28 | * are supported by HttpClient for proxy authentication. 29 | *

30 | */ 31 | public HttpProxy(TcpAddress address, Supplier> userPassSupplier) 32 | { 33 | this.address = address; 34 | this.userPassSupplier = userPassSupplier; 35 | } 36 | 37 | /** 38 | * Create an HttpProxy. 39 | *

40 | * This is a convenience method for 41 | * {@link #HttpProxy(bayou.tcp.TcpAddress, java.util.function.Supplier) HttpProxy(address, userPassSupplier)} 42 | * with address.ssl=false. 43 | *

44 | */ 45 | public HttpProxy(String host, int port, String username, String password) 46 | { 47 | this( 48 | new TcpAddress(false, host, port), 49 | ()->Async.success(new UserPass(username, password)) 50 | ); 51 | } 52 | 53 | /** 54 | * Create an HttpProxy. 55 | *

56 | * This is a convenience method for 57 | * {@link #HttpProxy(bayou.tcp.TcpAddress, java.util.function.Supplier) HttpProxy(address, userPassSupplier)} 58 | * with address.ssl=false, userPassSupplier=null. 59 | *

60 | */ 61 | public HttpProxy(String host, int port) 62 | { 63 | this( new TcpAddress(false, host, port), null); 64 | } 65 | 66 | 67 | /** 68 | * The proxy address. 69 | */ 70 | public TcpAddress address() 71 | { 72 | return address; 73 | } 74 | 75 | /** 76 | * The username/password supplier for proxy authentication. 77 | */ 78 | public Supplier> userPassSupplier() 79 | { 80 | return userPassSupplier; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/bayou/http/HttpResponseException.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | // received a bad response. 4 | // a response object may be included here for reference, but it may null, or its methods may malfunction 5 | public class HttpResponseException extends Exception 6 | { 7 | final HttpResponse response; // could be null or corrupt 8 | 9 | public HttpResponseException(String message, HttpResponse response) 10 | { 11 | super(message); 12 | 13 | this.response = response; 14 | } 15 | 16 | public HttpResponse response() 17 | { 18 | return response; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/bayou/http/HttpUpgrader.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import bayou.async.Async; 4 | import bayou.async.Fiber; 5 | import bayou.tcp.TcpConnection; 6 | 7 | /** 8 | * For handling HTTP Upgrade mechanism. 9 | *

10 | * See 11 | * RFC2616 §14.42 Upgrade. 12 | *

13 | *

14 | * An HttpUpgrader can be registered to an HttpServer by 15 | * {@link HttpServer#addUpgrader(String, HttpUpgrader) HttpServer.addUpgrader(protocol, httpUpgrader)}. 16 | * If an HttpRequest requests to upgrade the connection to that protocol, 17 | * the {@link #tryUpgrade(HttpRequest, bayou.tcp.TcpConnection)} method is invoked on the upgrader. 18 | * If the upgrade is successful, the upgrader takes over the connection and 19 | * is responsible to handle the connection by the new protocol. 20 | *

21 | */ 22 | public interface HttpUpgrader 23 | { 24 | // why not have a method returning supported protocols? 25 | // not sure about the return type. String seems fine. but some impl may need Set 26 | 27 | // invoked just before http server is started 28 | 29 | /** 30 | * Initialize this upgrader. 31 | *

32 | * This method is invoked before the HttpServer is started. 33 | *

34 | */ 35 | public void init(HttpServerConf httpServerConf) throws Exception; 36 | 37 | /** 38 | * Try to upgrade the http connection. 39 | *

40 | * This is an async action. If the action completes with {@code (HttpResponse)null}, 41 | * upgrade is successful, this HttpUpgrader takes over the TcpConnection. 42 | * If the action completes with a {@code non-null HttpResponse}, 43 | * the connection remains HTTP not-upgraded, and the HttpResponse 44 | * will be written to the client. If the action fails with an Exception, it's an internal error 45 | * and the connection will be killed. 46 | *

47 | *

48 | * If the `httpRequest` contains a body, the body should be drained first by `read()` till EOF, 49 | * before using the `tcpConnection` for new protocol. 50 | *

51 | *

52 | * This method is usually invoked in a {@link Fiber} created by the http server. 53 | * If upgrade is successful, that Fiber will end; a new Fiber or Fibers can be 54 | * created for the continued handling of the tcp connection. 55 | *

56 | */ 57 | public Async tryUpgrade(HttpRequest httpRequest, TcpConnection tcpConnection); 58 | // if the return value is null, we'll treat it as Async.success(null), 59 | // i.e. success takeover. however, don't advertise that. 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/bayou/http/RequestTarget.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import _bayou._http._HttpUtil; 4 | 5 | class RequestTarget 6 | { 7 | boolean isHttps; 8 | String host; 9 | String reqUri; // for HttpRequest.uri(), not absolute 10 | 11 | static RequestTarget of(boolean sslConn, String method, String reqTarget, String hvHost) 12 | { 13 | assert !method.equals("CONNECT"); // CONNECT is handled elsewhere 14 | 15 | RequestTarget rt = new RequestTarget(); 16 | 17 | rt.isHttps = sslConn; // may change. 18 | rt.host = hvHost; // may change. may be null or empty 19 | 20 | // request-target in 3 forms: origin, absolute, asterisk 21 | if (reqTarget.charAt(0) == '/') // origin-form, most common for HttpServer 22 | { 23 | if (!_HttpUtil.isOriginFormUri(reqTarget)) 24 | return null; 25 | rt.reqUri = reqTarget; 26 | } 27 | else if (reqTarget.equals("*")) // asterisk-form, only for OPTIONS. rare. 28 | { 29 | if (!method.equals("OPTIONS")) 30 | return null; 31 | rt.reqUri = reqTarget; 32 | } 33 | else // absolute-form. common for HttpRequestImpl. we only support absolute http/https URI. 34 | { 35 | String uriLo = reqTarget.toLowerCase(); 36 | if (uriLo.startsWith("http://")) 37 | rt.isHttps = false; 38 | else if (uriLo.startsWith("https://")) 39 | rt.isHttps = true; 40 | else 41 | return null; 42 | // request scheme was determined by connection type; now it's determined by request-target scheme. 43 | // if the two disagree, we use request-target scheme; client may lie about it, we accept it. 44 | 45 | int iHost = rt.isHttps ? 8 : 7; 46 | int iSlash = uriLo.indexOf('/', iHost); 47 | int iQuest = uriLo.indexOf('?', iHost); 48 | if (iSlash == -1 && iQuest == -1) // http://abc.com 49 | { 50 | rt.host = uriLo.substring(iHost); 51 | rt.reqUri = method.equals("OPTIONS") ? "*" : "/"; 52 | } 53 | else if (iSlash != -1 && (iQuest == -1 || iQuest > iSlash)) // http://abc.com/foo, http://abc.com/foo?bar 54 | { 55 | rt.host = uriLo.substring(iHost, iSlash); 56 | rt.reqUri = reqTarget.substring(iSlash); 57 | if (!_HttpUtil.isOriginFormUri(rt.reqUri)) 58 | return null; 59 | } 60 | else // http://abc.com?foo , http://abc.com?foo/bar 61 | { 62 | rt.host = uriLo.substring(iHost, iQuest); 63 | rt.reqUri = "/" + reqTarget.substring(iQuest); 64 | if (!_HttpUtil.isOriginFormUri(rt.reqUri)) 65 | return null; 66 | } 67 | // HTTP/1.1 client should send Host header identical to host from request-target. 68 | // we'll simply add/override Host with host from request-target. 69 | } 70 | return rt; 71 | // host is not validated here. 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/bayou/http/SimpleHttpEntity.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import bayou.bytes.ByteSource; 4 | import bayou.bytes.SimpleByteSource; 5 | import bayou.mime.ContentType; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.time.Instant; 9 | import java.util.ArrayList; 10 | import java.util.stream.Stream; 11 | 12 | /** 13 | * An HttpEntity of some bytes. 14 | *

15 | * The bytes are supplied to the constructor, as the body of the entity. 16 | *

17 | *

18 | * This is a sharable entity. 19 | *

20 | */ 21 | public class SimpleHttpEntity implements HttpEntity 22 | { 23 | final ContentType contentType; // may be null. 24 | final Instant lastModified; 25 | 26 | final ArrayList bbs; 27 | final Long bodyLength; 28 | 29 | /** 30 | * Create an HttpEntity with `bytes` as the body. 31 | */ 32 | public SimpleHttpEntity(ContentType contentType, Stream bytes) 33 | { 34 | // contentType may be null. we don't care. 35 | 36 | this.contentType = contentType; 37 | this.lastModified = Instant.now(); 38 | 39 | ArrayList list = new ArrayList<>(); 40 | long[] sum={0}; 41 | 42 | bytes.forEach(bb -> { 43 | list.add(bb); 44 | sum[0] += bb.remaining(); 45 | }); 46 | 47 | this.bbs = list; 48 | this.bodyLength = sum[0]; 49 | } 50 | 51 | /** 52 | * Create an HttpEntity with `bytes` as the body. 53 | */ 54 | public SimpleHttpEntity(ContentType contentType, ByteBuffer bytes) 55 | { 56 | this(contentType, Stream.of( bytes )); 57 | } 58 | 59 | /** 60 | * Create an HttpEntity with `bytes` as the body. 61 | */ 62 | public SimpleHttpEntity(ContentType contentType, byte[] bytes) 63 | { 64 | this(contentType, ByteBuffer.wrap(bytes)); 65 | } 66 | 67 | 68 | /** 69 | * The body of this entity. 70 | */ 71 | @Override 72 | public ByteSource body() 73 | { 74 | // entity can be shared. getBody() can be called multiple times. 75 | // SimpleByteSource will not modify buffers. 76 | return new SimpleByteSource(bbs.stream()); 77 | } 78 | 79 | /** 80 | * The content type. 81 | */ 82 | @Override 83 | public ContentType contentType() 84 | { 85 | return contentType; 86 | } 87 | 88 | /** 89 | * The length of the body. 90 | */ 91 | @Override 92 | public Long contentLength() 93 | { 94 | return bodyLength; 95 | } 96 | 97 | /** 98 | * When this entity was last modified. 99 | *

100 | * This implementation returns the time this object was instantiated. 101 | *

102 | */ 103 | @Override public Instant lastModified() 104 | { 105 | return lastModified; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/bayou/http/SimpleHttpResponse.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | 4 | import bayou.async.Async; 5 | import bayou.async.AutoAsync; 6 | import bayou.bytes.ByteSource; 7 | import bayou.mime.ContentType; 8 | import bayou.util.End; 9 | 10 | import java.nio.ByteBuffer; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * A simple HttpResponse. 17 | */ 18 | public class SimpleHttpResponse implements HttpResponse, AutoAsync 19 | { 20 | HttpStatus status; 21 | SimpleEntity entity; 22 | 23 | public SimpleHttpResponse(HttpStatus status, ContentType contentType, byte[] bytes) 24 | { 25 | this.status = status; 26 | this.entity = new SimpleEntity(contentType, bytes); 27 | } 28 | 29 | @Override 30 | public HttpStatus status() 31 | { 32 | return status; 33 | } 34 | 35 | @Override 36 | public Map headers() 37 | { 38 | return Collections.emptyMap(); 39 | } 40 | 41 | @Override 42 | public List cookies() 43 | { 44 | return Collections.emptyList(); 45 | } 46 | 47 | @Override 48 | public HttpEntity entity() 49 | { 50 | return entity; 51 | } 52 | 53 | static class SimpleEntity implements HttpEntity 54 | { 55 | ContentType contentType; 56 | byte[] bytes; 57 | 58 | SimpleEntity(ContentType contentType, byte[] bytes) 59 | { 60 | this.contentType = contentType; 61 | this.bytes = bytes; 62 | } 63 | 64 | @Override 65 | public ByteSource body() throws IllegalStateException 66 | { 67 | return new SimpleBody(bytes); 68 | } 69 | 70 | @Override 71 | public Long contentLength() 72 | { 73 | return (long)bytes.length; 74 | } 75 | 76 | @Override 77 | public ContentType contentType() 78 | { 79 | return contentType; 80 | } 81 | } 82 | 83 | static class SimpleBody implements ByteSource 84 | { 85 | byte[] bytes; 86 | 87 | SimpleBody(byte[] bytes) 88 | { 89 | this.bytes = bytes; 90 | } 91 | 92 | @Override 93 | public Async read() 94 | { 95 | if(bytes==null) 96 | return End.async(); 97 | 98 | Async abb = Async.success(ByteBuffer.wrap(bytes)); 99 | bytes=null; 100 | return abb; 101 | } 102 | 103 | @Override 104 | public Async close() 105 | { 106 | return Async.VOID; 107 | } 108 | } 109 | 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/bayou/http/ThrottledHttpEntity.java: -------------------------------------------------------------------------------- 1 | package bayou.http; 2 | 3 | import bayou.bytes.ByteSource; 4 | import bayou.bytes.ThrottledByteSource; 5 | 6 | /** 7 | * Throttles the origin HttpEntity. 8 | *

9 | * The body of this entity will be the body of the origin entity, 10 | * but throttled for throughput. 11 | *

12 | *

13 | * See {@link ThrottledByteSource}. 14 | *

15 | *

16 | * This entity is sharable iff the origin entity is sharable. 17 | *

18 | */ 19 | public class ThrottledHttpEntity implements HttpEntityWrapper 20 | { 21 | HttpEntity origin; 22 | int bufferSize; 23 | ThrottledByteSource.Curve curve; 24 | 25 | /** 26 | * Create a throttled wrapper of the origin entity. 27 | *

28 | * See {@link ThrottledByteSource#ThrottledByteSource(ByteSource, int, ThrottledByteSource.Curve)}. 29 | *

30 | */ 31 | public ThrottledHttpEntity(HttpEntity origin, int bufferSize, ThrottledByteSource.Curve curve) 32 | { 33 | this.origin = origin; 34 | 35 | this.bufferSize = bufferSize; 36 | this.curve = curve; 37 | } 38 | 39 | /** 40 | * Create a throttled wrapper of the origin source, with a linear curve. 41 | *

42 | * See {@link ThrottledByteSource#ThrottledByteSource(ByteSource, int, long, long)} 43 | *

44 | */ 45 | public ThrottledHttpEntity(HttpEntity origin, int bufferSize, long b0, long bytesPerSecond) 46 | { 47 | this(origin, bufferSize, ThrottledByteSource.Curve.linear(b0, bytesPerSecond)); 48 | } 49 | 50 | 51 | /** 52 | * The origin entity. 53 | */ 54 | @Override 55 | public HttpEntity getOriginEntity() 56 | { 57 | return origin; 58 | } 59 | 60 | /** 61 | * The entity body. 62 | *

63 | * The body of this entity will be the body of the origin entity, 64 | * but throttled for throughput. 65 | *

66 | */ 67 | @Override 68 | public ByteSource body() 69 | { 70 | ByteSource originBody = origin.body(); 71 | // next line doesn't throw, or we need to close originBody 72 | return new ThrottledByteSource(originBody, bufferSize, curve); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/bayou/http/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Http server. 3 | */ 4 | package bayou.http; -------------------------------------------------------------------------------- /src/bayou/mime/Headers.java: -------------------------------------------------------------------------------- 1 | package bayou.mime; 2 | 3 | /** 4 | * Well-known MIME header names. 5 | *

6 | * Every constant String field in this interface corresponds to the name of a well-known MIME header, 7 | * for example, {@link #Accept_Charset Headers.Accept_Charset}="Accept-Charset". 8 | * Note that dash("-") in the header name is replaced with underscore("_") in the field name. 9 | *

10 | */ 11 | public interface Headers 12 | { 13 | // all "public static final String" fields must be header name constants. 14 | // class _KnownHeaders depends on that fact when compiling the list of all headers. 15 | 16 | // http headers from rfc 2616 17 | public static final String 18 | Accept="Accept", Accept_Charset="Accept-Charset", Accept_Encoding="Accept-Encoding", 19 | Accept_Language="Accept-Language", Accept_Ranges="Accept-Ranges", Age="Age", Allow="Allow", 20 | Authorization="Authorization", Cache_Control="Cache-Control", Connection="Connection", 21 | Content_Encoding="Content-Encoding", Content_Language="Content-Language", 22 | Content_Length="Content-Length", Content_Location="Content-Location", Content_MD="Content-MD", 23 | Content_Range="Content-Range", Content_Type="Content-Type", Date="Date", ETag="ETag", Expect="Expect", 24 | Expires="Expires", From="From", Host="Host", If_Match="If-Match", If_Modified_Since="If-Modified-Since", 25 | If_None_Match="If-None-Match", If_Range="If-Range", If_Unmodified_Since="If-Unmodified-Since", 26 | Last_Modified="Last-Modified", Location="Location", Max_Forwards="Max-Forwards", Pragma="Pragma", 27 | Proxy_Authenticate="Proxy-Authenticate", Proxy_Authorization="Proxy-Authorization", Range="Range", 28 | Referer="Referer", Retry_After="Retry-After", Server="Server", TE="TE", Trailer="Trailer", 29 | Transfer_Encoding="Transfer-Encoding", Upgrade="Upgrade", User_Agent="User-Agent", Vary="Vary", 30 | Via="Via", Warning="Warning", WWW_Authenticate="WWW-Authenticate"; 31 | 32 | public static final String 33 | Set_Cookie="Set-Cookie", Cookie="Cookie", Keep_Alive="Keep-Alive", 34 | Content_Disposition="Content-Disposition", 35 | Origin="Origin"; 36 | 37 | public static final String 38 | X_Forwarded_For = "X-Forwarded-For", X_Forwarded_Proto = "X-Forwarded-Proto"; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/bayou/mime/MultipartPart.java: -------------------------------------------------------------------------------- 1 | package bayou.mime; 2 | 3 | import bayou.bytes.ByteSource; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * A part in a multipart body. 9 | *

10 | * A part contains some headers, and a sequence of bytes as the part body. 11 | * See RFC2046. 12 | *

13 | */ 14 | 15 | // see html4/5 -> rfc2388 -> rfc2046 16 | // http rfc2616 -> rfc2046 17 | 18 | public interface MultipartPart 19 | { 20 | /** 21 | * The headers of this part. 22 | *

23 | * The returned Map is read-only; keys are case-insensitive for lookup. 24 | *

25 | */ 26 | Map headers(); 27 | // name/value chars must be octets, i.e. 0x00-0xFF. 28 | // if needed, app can encode a string to bytes, then disguise it as Latin-1 string. 29 | // name chars are print chars except COLON. value chars are any octets except CR LF. 30 | // see _CharDef.Rfc822 31 | 32 | /** 33 | * The body of this part. 34 | *

35 | * This method may be invoked only once in some implementations. 36 | *

37 | */ 38 | ByteSource body(); 39 | // each body() call should return a new independent ByteSource at pos=0. 40 | // if not possible, only 1st call will succeed; other calls throw IllegalStateException 41 | } 42 | -------------------------------------------------------------------------------- /src/bayou/mime/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIME related utilities. 3 | */ 4 | package bayou.mime; -------------------------------------------------------------------------------- /src/bayou/reload/HotCompiler.java: -------------------------------------------------------------------------------- 1 | package bayou.reload; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.PathMatcher; 5 | import java.util.Set; 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * Compiler for HotReloader. 10 | *

11 | * A HotCompiler is registered to a HotReloader through 12 | * {@link HotReloader#addCompiler(HotCompiler,boolean,Path...) HotReloader.addCompiler(compiler, reload, srcDirs)}. 13 | * The source files for the compiler are those under the srcDirs and matching 14 | * {@link #getPathMatcher() the PathMatcher}. 15 | *

16 | *

17 | * When source files are created/updated/deleted, the changes are fed to the 18 | * {@link #compile(Consumer, Set, Set, Set) compile(createdFiles,updatedFiles,deletedFiles)} 19 | * method for recompilation. There is also a 20 | * {@link #compile(Consumer, Set) compile(allFiles)} method for recompiling all source files. 21 | *

22 | *

23 | * A HotCompiler usually compiles source files to output files. 24 | * However it may do something else, e.g. update an external device after a source file is updated. 25 | *

26 | *

27 | * An implementation of HotCompiler can be stateful, and not thread-safe. 28 | * A HotCompiler instance should not be shared, i.e. it should not be used in multiple 29 | * {@link HotReloader#addCompiler(HotCompiler,boolean,Path...) addCompiler()} calls. 30 | *

31 | */ 32 | public interface HotCompiler 33 | { 34 | /** 35 | * The PathMatcher for source files. 36 | *

37 | * Only those files under the {@link HotReloader#addCompiler(HotCompiler,boolean,Path...) srcDirs } 38 | * will be fed to this PathMatcher; 39 | * only those approved by this PathMatcher are source files for this compiler. 40 | *

41 | *

42 | * An example PathMatcher: 43 | * FileSystems.getDefault().getPathMatcher("glob:**.java"). 44 | *

45 | * @see java.nio.file.FileSystem#getPathMatcher(String) 46 | */ 47 | PathMatcher getPathMatcher(); 48 | // pathMatcher is specified by the HotCompiler instead of by HotReloader.addCompiler() 49 | // it's usually an inherent property of the compiler, app may not get it right. 50 | // a compiler may work on fixed srcDirs (see JavacCompiler). we don't put srcDirs on this interface, 51 | // reasoning that it's not unlikely that a compiler works on any given srcDirs, 52 | // therefore app should be able to specific srcDirs independent of the compiler. 53 | 54 | /** 55 | * Compile all source files. 56 | *

57 | * This method is usually called in the initial phase of the HotReloader. 58 | * It may also be called whenever the HotReloader thinks necessary. 59 | *

60 | *

61 | * The compiler does not necessarily do a clean build. For example, it may compare 62 | * timestamps of source files and output files, skip source files that are older than the output files. 63 | *

64 | * @param msgOut 65 | * for diagnosis messages; see {@link HotReloader#getMessageOut()} 66 | * @throws Exception 67 | * if compilation fails; the exception message usually contains compilation errors. 68 | */ 69 | void compile(Consumer msgOut, Set allFiles) throws Exception; 70 | // after compile, sources and outputs should be in sync. (should remove undesired outputs from prev compiles) 71 | 72 | /** 73 | * Compile changed source files. 74 | *

75 | * This method is called if some source files are created/updated/deleted. 76 | *

77 | * @param msgOut 78 | * for diagnosis messages; see {@link HotReloader#getMessageOut()} 79 | * @throws Exception 80 | * if compilation fails; the exception message usually contains compilation errors. 81 | */ 82 | // incremental compile. when possible, builder uses this method to reduce cost. 83 | void compile(Consumer msgOut, 84 | Set createdFiles, Set updatedFiles, Set deletedFiles) 85 | throws Exception; 86 | } 87 | -------------------------------------------------------------------------------- /src/bayou/reload/VoidCompiler.java: -------------------------------------------------------------------------------- 1 | package bayou.reload; 2 | 3 | import java.nio.file.FileSystems; 4 | import java.nio.file.Path; 5 | import java.nio.file.PathMatcher; 6 | import java.util.Set; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * A HotCompiler that generates no output. 11 | *

12 | * The purpose of a VoidCompiler is to reload the application if some files are changed 13 | * (but requiring no recompilation). For example, if the application reads ".properties" files 14 | * on startup and cache the information in memory, it needs to be reloaded if some of the files are changed. 15 | *

16 | *

17 | * See also 18 | * {@link HotReloader#onFiles(String, String, String...) HotReloader.onFiles(fileDesc, filePattern, srcDirs)}. 19 | *

20 | */ 21 | 22 | // a compiler that does nothing 23 | // primary use: reload app after some files are changed; app reads these files during init. 24 | // addCompiler(new VoidCompiler("glob:**.properties"), srcDirs); 25 | // //will reload app if any .properties files are create/updated/deleted under srcDirs. 26 | // to monitor a classes dir, and reload app when classes changes 27 | // addCompiler(new VoidCompiler("glob:**.class"), classDirs); 28 | 29 | public class VoidCompiler implements HotCompiler 30 | { 31 | final String fileDesc; 32 | final PathMatcher pathMatcher; 33 | 34 | /** 35 | * Create a VoidCompiler. 36 | *

37 | * fileDesc is used for diagnosis outputs. 38 | *

39 | *

40 | * For example: 41 | * new VoidCompiler("java", FileSystems.getDefault().getPathMatcher("glob:**.java")) 42 | *

43 | */ 44 | public VoidCompiler(String fileDesc, PathMatcher pathMatcher) 45 | { 46 | this.fileDesc = fileDesc; 47 | this.pathMatcher = pathMatcher; 48 | } 49 | 50 | static VoidCompiler of(String fileDesc, String filePattern) 51 | { 52 | PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(filePattern); 53 | return new VoidCompiler(fileDesc, pathMatcher); 54 | } 55 | 56 | // return a path matcher based on the filePattern 57 | @Override 58 | public PathMatcher getPathMatcher() 59 | { 60 | return pathMatcher; 61 | } 62 | 63 | /** 64 | * Does nothing, except printing some messages to msgOut. 65 | */ 66 | @Override 67 | public void compile(Consumer msgOut, Set allFiles) 68 | { 69 | msgOut.accept(fileDesc + " files: " + allFiles.size() + " total"); 70 | } 71 | 72 | /** 73 | * Does nothing, except printing some messages to msgOut. 74 | */ 75 | @Override 76 | public void compile(Consumer msgOut, 77 | Set createdFiles, Set updatedFiles, Set deletedFiles) 78 | { 79 | if(createdFiles.size()>0) 80 | msgOut.accept(fileDesc + " files: " + createdFiles.size() + " created"); 81 | if(updatedFiles.size()>0) 82 | msgOut.accept(fileDesc + " files: " + updatedFiles.size() + " updated"); 83 | if(deletedFiles.size()>0) 84 | msgOut.accept(fileDesc + " files: " + deletedFiles.size() + " deleted"); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/bayou/reload/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Hot-reload support. 3 | */ 4 | package bayou.reload; -------------------------------------------------------------------------------- /src/bayou/ssl/SslConnection.java: -------------------------------------------------------------------------------- 1 | package bayou.ssl; 2 | 3 | import bayou.tcp.TcpConnection; 4 | 5 | import javax.net.ssl.SSLSession; 6 | import java.nio.ByteBuffer; 7 | 8 | /** 9 | * Non-blocking SSL connection. 10 | *

11 | * Note that this interface is a subtype of {@link bayou.tcp.TcpConnection}. 12 | *

13 | *

14 | * See {@link bayou.ssl.SslChannel2Connection} for converting a TcpChannel to an SslConnection. 15 | *

16 | */ 17 | public interface SslConnection extends TcpConnection 18 | { 19 | 20 | /** 21 | * Sentinel value for 22 | * {@link bayou.tcp.TcpConnection#read()} and 23 | * {@link bayou.tcp.TcpConnection#queueWrite(java.nio.ByteBuffer)}, 24 | * representing SSL close-notify record. 25 | */ 26 | ByteBuffer SSL_CLOSE_NOTIFY = ByteBuffer.wrap(new byte[0]); 27 | 28 | 29 | /** 30 | * Get the SSL session. 31 | */ 32 | SSLSession getSslSession(); 33 | // we only expose the SSLSession, instead of the SSLEngine. 34 | // other methods in SSLEngine are probably uninteresting to app. 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/bayou/ssl/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * SSL support. 3 | */ 4 | package bayou.ssl; -------------------------------------------------------------------------------- /src/bayou/tcp/AsyncConnection.java: -------------------------------------------------------------------------------- 1 | package bayou.tcp; 2 | 3 | import bayou.async.Async; 4 | 5 | import java.net.InetAddress; 6 | import java.nio.ByteBuffer; 7 | import java.time.Duration; 8 | import java.util.concurrent.Executor; 9 | 10 | // abstract tcp/ssl socket channel. async interface 11 | 12 | // support EOF, drain. 13 | // example: HTTP server should do "lingering close": first half-close; then drain; then close. 14 | // translated into our API: queue(EOF), write() till wr=0, close( drainTimeout>0 ) 15 | 16 | // thread safe; all methods can be called at any time on any thread. 17 | // however no concurrent pending read/write 18 | 19 | // unread/getWriteRemaining/queueWrite/queue(EOF) after close() will throw IllegalStateException 20 | // not necessarily a programming error, the channel may be closed async-ly, so it's not caller's fault. 21 | // we could just ignore the call. but we throw instead so caller can know what's going on. 22 | 23 | interface AsyncConnection 24 | { 25 | InetAddress getRemoteIp(); 26 | 27 | // ---- 28 | 29 | // if successful, something available to read. (successful read). 30 | // guarantee: bb.length()>0 (unless an empty bb was given in unread()) 31 | // if fail with End, EOF 32 | // if fail with other error, usually unrecoverable, corrupt. source should then be closed. 33 | Async read(boolean accepting) throws IllegalStateException; 34 | 35 | // next read() will return `bb` as is (unless close() called before read()) 36 | // bb can be empty, we don't check 37 | // only 1 unread() is supported. 38 | // cannot unread() while a read is pending. 39 | void unread(ByteBuffer bb) throws IllegalStateException; 40 | // this method is very useful to apps; often read() may return more data than currently needed. 41 | 42 | // ---- 43 | 44 | // the async channel maintains a queue of ByteBuffer to be written. write() will attempt to write them. 45 | // (this mechanism is useful/helpful to user. and we need it for ssl multi-stage buffering.) 46 | // write queue size (wqs) is the number of bytes not written yet. 47 | 48 | long getWriteQueueSize() throws IllegalStateException; 49 | 50 | // returns write queue size. 51 | // bytes may be very small, e.g. "\r\n", or very large, e.g. some giant cached data in one piece. 52 | // CAUTION: conn now owns bb, and may update it's position etc. use duplicate if necessary 53 | // can be invoked during write pending 54 | long queueWrite(ByteBuffer bb) throws IllegalStateException; 55 | // can queue FIN/CLOSE_NOTIFY. probably should have new methods for that. 56 | 57 | // try to write n bytes or more 58 | // write is aggressive - after n bytes, we'll write as much as possible as long as it's not blocking 59 | // 0wr case; caller probably miscalculated 62 | // return: actual bytes written 63 | // remember EOF counts as 1 byte. 64 | // if error, likely unrecoverable, some bytes may have been written. state may be corrupt. caller should close() 65 | Async write(long n) throws IllegalStateException; 66 | 67 | // ---- 68 | 69 | Executor getExecutor(); 70 | 71 | 72 | // can be called multiple times. can be called anytime on any thread. 73 | // (only 1st call is effective, particularly, only the drainTimeout arg of the 1st call is effective) 74 | // pending read/write will complete with exception 75 | // abandon any buffered writes. 76 | // 77 | // if drainTimeout is null or <=0, no drain 78 | // if drainTimeout>0, will attempt to drain inbound data (raw tcp) before closing the TCP connection. 79 | // this is to prevent the RST problem - if inbound data comes after close. 80 | // a properly designed app protocol may not need to drain, because it knows there's no more inbound data. 81 | // though draining can also be done by read(), it might be a little expensive (e.g. decoding SSL records) 82 | // also no need to drain if EOF was read; an error occurred; app wants to kill conn imm. 83 | // this method does not send EOF to peer before draining; use queue(EOF)+write() instead. 84 | void close(Duration drainTimeout); 85 | } 86 | -------------------------------------------------------------------------------- /src/bayou/tcp/TcpAddress.java: -------------------------------------------------------------------------------- 1 | package bayou.tcp; 2 | 3 | /** 4 | * Address of a TCP service. 5 | *

6 | * The address includes {@link #host() host} and {@link #port() port}. 7 | * Additionally, {@link #ssl() ssl}=true if SSL is required by the service. 8 | *

9 | */ 10 | public class TcpAddress 11 | { 12 | final boolean ssl; 13 | final String host; // domain or ip literal. lower case 14 | final int port; 15 | 16 | /** 17 | * Create an instance. 18 | */ 19 | public TcpAddress(boolean ssl, String host, int port) 20 | { 21 | // todo: validate host and port? 22 | this.ssl = ssl; 23 | this.host = host.toLowerCase(); 24 | this.port = port; 25 | } 26 | /** 27 | * Create an instance. 28 | *

29 | * This constructor is equivalent to 30 | * {@link #TcpAddress(boolean, String, int) TcpAddress(false, host, port)}. 31 | *

32 | */ 33 | public TcpAddress(String host, int port) 34 | { 35 | this(false, host, port); 36 | } 37 | 38 | /** 39 | * Whether SSL is required. 40 | */ 41 | public boolean ssl() 42 | { 43 | return ssl; 44 | } 45 | 46 | /** 47 | * The TCP host, either a domain name or an IP literal, in lower case. 48 | */ 49 | public String host() 50 | { 51 | return host; 52 | } 53 | 54 | /** 55 | * The TCP port. 56 | */ 57 | public int port() 58 | { 59 | return port; 60 | } 61 | 62 | @Override 63 | public int hashCode() 64 | { 65 | return (ssl?31*31:0) + 31*host.hashCode() + port; 66 | } 67 | 68 | /** 69 | * Return true iff `obj` is a TcpAddress with the same `ssl`, `host`, and `port`. 70 | */ 71 | public boolean equals(Object obj) 72 | { 73 | if(!(obj instanceof TcpAddress)) 74 | return false; 75 | 76 | TcpAddress that = (TcpAddress)obj; 77 | return this.ssl==that.ssl 78 | && this.port==that.port 79 | && this.host.equals(that.host); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/bayou/tcp/TcpChannel2Connection.java: -------------------------------------------------------------------------------- 1 | package bayou.tcp; 2 | 3 | import _bayou._tmp._ByteBufferPool; 4 | import _bayou._tmp._Tcp; 5 | import bayou.util.function.FunctionX; 6 | 7 | import java.util.concurrent.atomic.AtomicLong; 8 | import java.util.function.Supplier; 9 | 10 | /** 11 | * Convert TcpChannel to TcpConnection. 12 | *

13 | * This class is for "plain" connections. 14 | * See {@link bayou.ssl.SslChannel2Connection} instead for SSL connections. 15 | *

16 | */ 17 | public class TcpChannel2Connection 18 | { 19 | 20 | final Supplier idGenerator; 21 | 22 | final _ByteBufferPool plainReadBufferPool; 23 | final _ByteBufferPool plainWriteBufferPool; 24 | 25 | /** Create a TcpChannel to TcpConnection converter. 26 | * 27 | * @param readBufferSize 28 | * preferred buffer size for calling {@link bayou.tcp.TcpChannel#read(java.nio.ByteBuffer)}. 29 | * @param writeBufferSize 30 | * preferred buffer size for calling {@link bayou.tcp.TcpChannel#write(java.nio.ByteBuffer...)}. 31 | */ 32 | public TcpChannel2Connection(int readBufferSize, int writeBufferSize) 33 | { 34 | this.idGenerator = _Tcp.idGenerator; 35 | 36 | // read/writeBufferSize - not to be confused with socket receive/send buffer size. 37 | plainReadBufferPool = _ByteBufferPool.forCapacity(readBufferSize); 38 | plainWriteBufferPool = _ByteBufferPool.forCapacity(writeBufferSize); 39 | // use default expiration for buffer pools 40 | } 41 | 42 | /** 43 | * Convert a TcpChannel to a TcpConnection. 44 | */ 45 | public TcpConnection convert(TcpChannel channel) 46 | { 47 | long id = idGenerator.get().longValue(); 48 | return new PlainTcpConnection(channel, id, plainReadBufferPool, plainWriteBufferPool); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/bayou/tcp/TcpTunnel.java: -------------------------------------------------------------------------------- 1 | package bayou.tcp; 2 | 3 | import bayou.async.Async; 4 | 5 | /** 6 | * A tunnel forwarding data between TCP clients and servers. 7 | *

8 | * A tunnel sits between a client and a server, forwarding data in both directions. 9 | * For example a SOCKS proxy is a TCP tunnel. 10 | *

11 | *

12 | * This interface is from the client's perspective. 13 | * The client first establishes a `connection` to the tunnel's {@link #address()}, 14 | * then calls {@link #tunnelTo(TcpConnection, String, int) tunnelTo(connection, host, port)} 15 | * to instruct it to tunnel to the server. 16 | *

17 | */ 18 | public interface TcpTunnel 19 | { 20 | 21 | /** 22 | * The address of the tunnel service. 23 | *

24 | * If {@link TcpAddress#ssl() address.ssl}==true, 25 | * traffic between the client and the tunnel must be encrypted in SSL. 26 | *

27 | */ 28 | TcpAddress address(); 29 | 30 | /** 31 | * Tunnel to the remote host:port. 32 | *

33 | * The `connection` is established between the client and the tunnel service. 34 | * The implementation of this method does necessary protocol dance to 35 | * instruct the tunnel service to tunnel to the server (at host:port). 36 | *

37 | *

38 | * When this action succeeds, the resulting TcpConnection is used by the client 39 | * to communicate to the server, as if it's a direct connection. 40 | * The resulting TcpConnection could be the same object as the `connection` argument. 41 | *

42 | */ 43 | Async tunnelTo(TcpConnection connection, String host, int port); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/bayou/tcp/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Non-blocking TCP server. 3 | */ 4 | package bayou.tcp; -------------------------------------------------------------------------------- /src/bayou/text/TextDoc.java: -------------------------------------------------------------------------------- 1 | package bayou.text; 2 | 3 | import _bayou._str._CharSeqSaver; 4 | import bayou.mime.ContentType; 5 | 6 | import java.nio.charset.Charset; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Textual document, for example text/html, text/plain. 11 | *

12 | * A TextDoc has a {@link ContentType}, 13 | * and a content body which is a sequence of chars. 14 | *

15 | */ 16 | public interface TextDoc 17 | { 18 | // e.g. "text/html;charset=UTF-8" 19 | 20 | /** 21 | * Get the content type of this document, for example "text/html;charset=UTF-8". 22 | *

23 | * The content type should contain a "charset" parameter that is consistent with 24 | * {@link #getCharset()}. 25 | *

26 | */ 27 | ContentType getContentType(); 28 | 29 | /** 30 | * Get the charset of this document, for example "UTF-8". 31 | *

32 | * The charset should be consistent with {@link #getContentType()}. 33 | *

34 | */ 35 | // usually a doc knows better about it's charset. 36 | // user of the doc doesn't have to honor the charset. he can use a diff one for chars->bytes 37 | Charset getCharset(); 38 | 39 | /** 40 | * Print the content body to `out`. 41 | *

42 | * The implementation of this method may invoke `out.accept()` multiple times. 43 | *

44 | */ 45 | void getContentBody(Consumer out); 46 | 47 | /** 48 | * Get the content body. 49 | *

50 | * This default implementation invokes {@link #getContentBody(Consumer) getContentBody(out)} 51 | * and merges outputs to one char sequence. 52 | *

53 | */ 54 | default CharSequence getContentBody() 55 | { 56 | _CharSeqSaver out = new _CharSeqSaver(256); 57 | getContentBody(out); 58 | return out.toCharSequence(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/bayou/text/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities for texts. 3 | */ 4 | package bayou.text; -------------------------------------------------------------------------------- /src/bayou/util/End.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | import bayou.async.Async; 4 | import bayou.bytes.ByteSource; 5 | import bayou.websocket.WebSocketClose; 6 | 7 | /** 8 | * A control exception for signaling that something has reached its end. 9 | *

10 | * For example, {@link bayou.async.AsyncIterator#next()} either succeeds with the next item, 11 | * or fails with an End if there are no more items. 12 | *

13 | *

14 | * Other APIs, for example {@link ByteSource#read()}, also use End 15 | * as the control exception, to be compatible with {@link bayou.async.AsyncIterator#next()}. 16 | *

17 | *

18 | * Some APIs create subtypes of End to carry more information. 19 | * For example, {@link WebSocketClose}, a subtype of End, 20 | * contains code/reason for the closure of a WebSocket channel inbound/outbound. 21 | *

22 | *

23 | * Being a control exception, an End does not really represent an exceptional condition; 24 | * it is more of a sentinel value for an API. 25 | * An End contains no stacktrace and is cheap to create. 26 | * It is immutable, 27 | * with enableSuppression=writableStackTrace=false, 28 | * see explanation in 29 | * {@link Throwable#Throwable(String, Throwable, boolean, boolean) 30 | * Throwable(message, cause, enableSuppression, writableStackTrace)}. 31 | * It's safe to share an End object due to immutability. 32 | *

33 | */ 34 | 35 | public class End extends Exception 36 | { 37 | /** 38 | * Create an End exception. 39 | */ 40 | public End() 41 | { 42 | super(null, null, false, false); 43 | } 44 | 45 | /** 46 | * Create an End exception. 47 | */ 48 | public End(String message) 49 | { 50 | super(message, null, false, false); 51 | } 52 | 53 | /** 54 | * Create an End exception. 55 | */ 56 | public End(String message, Throwable cause) 57 | { 58 | super(message, cause, false, false); 59 | } 60 | 61 | 62 | static final End INSTANCE = new End(); 63 | static final Async ASYNC_END = Result.failure(INSTANCE); 64 | 65 | /** 66 | * Return an End instance. 67 | *

68 | * Semantically equivalent to `new End()`, 69 | * but this method may return a cached object. 70 | *

71 | *

72 | * Example Usage: 73 | *

74 | *
 75 |      *     action
 76 |      *         .then( v->
 77 |      *         {
 78 |      *             if(condition)
 79 |      *                 throw End.instance();
 80 |      *             ...
 81 |      *         }
 82 |      * 
83 | */ 84 | public static End instance() 85 | { 86 | return INSTANCE; 87 | } 88 | 89 | /** 90 | * Return an `Async<T>` that immediately fails with an End exception. 91 | *

92 | * Semantically equivalent to 93 | * `Async.failure(new End())`, 94 | * but this method may return a cached object. 95 | *

96 | *

97 | * Often used as a return value in an Async method, for example: 98 | *

99 | *
100 |      *     public Async<ByteBuffer> read()
101 |      *     {
102 |      *         if( eof_reached )
103 |      *             return End.async();
104 |      *         ...
105 |      *     }
106 |      * 
107 | */ 108 | public static Async async() 109 | { 110 | @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) 111 | Async async = ASYNC_END; // erasure 112 | return async; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/bayou/util/OverLimitException.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | import bayou.bytes.ByteSource; 4 | 5 | /** 6 | * To indicate that a certain limit is exceeded. 7 | * 8 | *

9 | * For example, {@link ByteSource#readAll(int) ByteSource.allBytes(maxBytes)} reads all bytes 10 | * from the source into a ByteBuffer, up to a limit. 11 | * If we invoke source.allBytes(1024), and the total number of bytes in the source exceeds 1024, 12 | * the action fails with a new OverLimitException("maxBytes", 1024). 13 | *

14 | * 15 | *

16 | * Each OverLimitException contains the name of the limit (for example "maxBytes") 17 | * and the value of the limit (for example 1024). 18 | * The UI layer can use them to generate a more user-friendly error message, for example: 19 | * "The maximum number of bytes must not exceed 1024." 20 | *

21 | * 22 | *

23 | * Note that OverLimitException does not contain the information of 24 | * how much is exceeded beyond the limit. 25 | * Usually the action is aborted as soon as the limit is exceeded, 26 | * therefore it is unknown what the total amount would have been. 27 | *

28 | * 29 | */ 30 | public class OverLimitException extends Exception 31 | { 32 | final String limitName; 33 | final long limitValue; 34 | 35 | /** 36 | * Example: new OverLimitException("maxBytes", 1024) 37 | */ 38 | public OverLimitException(String limitName, long limitValue) 39 | { 40 | this(limitName, limitValue, limitName+"="+limitValue); 41 | } 42 | 43 | /** 44 | * Example: new OverLimitException("maxBytes", 1024, "number of bytes exceeds 1024") 45 | */ 46 | public OverLimitException(String limitName, long limitValue, String message) 47 | { 48 | super(message); 49 | 50 | this.limitName = limitName; 51 | this.limitValue = limitValue; 52 | } 53 | 54 | /** 55 | * The name of the limit, for example, "maxBytes". 56 | */ 57 | public String getLimitName() 58 | { 59 | return limitName; 60 | } 61 | 62 | /** 63 | * The value of the limit, for example, 1024. 64 | */ 65 | public long getLimitValue() 66 | { 67 | return limitValue; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/bayou/util/Result_Failure.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | import _bayou._async._Fiber_Stack_Trace_; 4 | import bayou.async.Fiber; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * An implementation of failure Result. 10 | */ 11 | class Result_Failure implements Result 12 | { 13 | final Exception exception; 14 | 15 | /** 16 | * Create a Failure Result. 17 | * @param exception 18 | * the failure exception. Must be non-null. 19 | */ 20 | public Result_Failure(Exception exception) 21 | { 22 | Objects.requireNonNull(exception); 23 | 24 | if(Fiber.enableTrace) 25 | _Fiber_Stack_Trace_.addFiberStackTrace(exception, Fiber.current()); // fiber may be null 26 | 27 | this.exception = exception; 28 | } 29 | 30 | /** 31 | * Return false. 32 | */ 33 | @Override 34 | public boolean isSuccess() 35 | { 36 | return false; 37 | } 38 | 39 | /** 40 | * Return true. 41 | */ 42 | @Override 43 | public boolean isFailure() 44 | { 45 | return true; 46 | } 47 | 48 | /** 49 | * Return null, because this is a failure Result. 50 | */ 51 | @Override 52 | public T getValue() 53 | { 54 | return null; 55 | } 56 | 57 | /** 58 | * Return the failure exception. 59 | */ 60 | @Override 61 | public Exception getException() 62 | { 63 | return exception; 64 | } 65 | 66 | /** 67 | * Throw the failure exception. 68 | */ 69 | @Override 70 | public T getOrThrow() throws Exception 71 | { 72 | throw exception; 73 | } 74 | 75 | @Override 76 | public String toString() 77 | { 78 | return "Result:Failure: "+exception; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/bayou/util/Result_Success.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | /** 4 | * An implementation of success Result. 5 | */ 6 | class Result_Success implements Result 7 | { 8 | final T value; 9 | 10 | /** 11 | * Create a Success Result with the value. 12 | * @param value 13 | * the success value; Can be null. 14 | */ 15 | public Result_Success(T value) 16 | { 17 | // value can be null 18 | this.value = value; 19 | } 20 | 21 | /** 22 | * Return true. 23 | */ 24 | @Override 25 | public boolean isSuccess() 26 | { 27 | return true; 28 | } 29 | 30 | /** 31 | * Return false. 32 | */ 33 | @Override 34 | public boolean isFailure() 35 | { 36 | return false; 37 | } 38 | 39 | /** 40 | * Return the success value. 41 | */ 42 | @Override 43 | public T getValue() 44 | { 45 | return value; 46 | } 47 | 48 | /** 49 | * Return null, because this is a success Result. 50 | */ 51 | @Override 52 | public Exception getException() 53 | { 54 | return null; 55 | } 56 | 57 | /** 58 | * Return the success value. 59 | */ 60 | @Override 61 | public T getOrThrow() 62 | { 63 | return value; 64 | } 65 | 66 | @Override 67 | public String toString() 68 | { 69 | return "Result:Success: "+value; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/bayou/util/Tuple2.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | import java.util.Objects; 4 | 5 | class Tuple2 6 | { 7 | public final T1 v1; 8 | public final T2 v2; 9 | 10 | public Tuple2(T1 v1, T2 v2) 11 | { 12 | this.v1 = v1; 13 | this.v2 = v2; 14 | } 15 | 16 | public static Tuple2 of(T1 v1, T2 v2) 17 | { 18 | return new Tuple2<>(v1, v2); 19 | } 20 | 21 | public T1 getV1() 22 | { 23 | return v1; 24 | } 25 | 26 | public T2 getV2() 27 | { 28 | return v2; 29 | } 30 | 31 | @Override 32 | public int hashCode() 33 | { 34 | // see List.hashCode 35 | return 31*31 + 31*Objects.hashCode(v1) + Objects.hashCode(v2); 36 | } 37 | 38 | @Override 39 | public boolean equals(Object obj) 40 | { 41 | if(!(obj instanceof Tuple2)) 42 | return false; 43 | 44 | Tuple2 that = (Tuple2)obj; 45 | return Objects.equals(this.v1, that.v1) 46 | && Objects.equals(this.v2, that.v2); 47 | } 48 | 49 | @Override 50 | public String toString() 51 | { 52 | return "Pair["+v1+","+v2+"]"; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/bayou/util/UserPass.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | /** 4 | * Username and password. 5 | */ 6 | public class UserPass 7 | { 8 | final String username; 9 | final String password; 10 | 11 | /** 12 | * Create an instance. 13 | */ 14 | public UserPass(String username, String password) 15 | { 16 | this.username = username; 17 | this.password = password; 18 | } 19 | 20 | /** 21 | * The username. 22 | */ 23 | public String username() 24 | { 25 | return username; 26 | } 27 | 28 | /** 29 | * The password. 30 | */ 31 | public String password() 32 | { 33 | return password; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/bayou/util/Var.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.Supplier; 5 | 6 | /** 7 | * A variable that can be read from and written to. 8 | * 9 | *

10 | * See {@link #get()} and {@link #set(Object) set(T)} methods. 11 | *

12 | *

13 | * The set() method may opt to throw RuntimeException for whatever reason. 14 | *

15 | * 16 | *

hashCode(), equals(), toString()

17 | *

18 | * These methods on a subclass must have the following semantics 19 | *

20 | *
 21 |     public int hashCode()
 22 |     {
 23 |         return Objects.hashCode(get());
 24 |     }
 25 |     public boolean equals(Object obj)
 26 |     {
 27 |         return (obj instanceof Var) && Objects.equals(this.get(), ((Var<?>)obj).get());
 28 |     }
 29 |     public String toString()
 30 |     {
 31 |         return String.valueOf(get());
 32 |     }
 33 |  * 
34 | *

35 | * A subclass can simply copy&paste these lines. 36 | * Unfortunately they cannot be default methods on this interface. 37 | *

38 | * 39 | */ 40 | public interface Var 41 | { 42 | 43 | /** 44 | * Read the value of this Var. 45 | */ 46 | public T get(); 47 | 48 | /** 49 | * Set the value of this Var, then return the value. 50 | *

51 | * Note that the new value, not the old one, is returned. 52 | * This is to be consistent with the semantics of Java assignment expression. 53 | *

54 | * @return the same `value` 55 | * @throws RuntimeException if the value cannot be set, for whatever reason. 56 | */ 57 | public T set(T value) throws RuntimeException; 58 | 59 | /** 60 | * Create a Var with the getter and setter. 61 | *

62 | * The get() and set() methods of the Var simply forward to the getter and setter. 63 | *

64 | */ 65 | public static Var of(Supplier getter, Consumer setter) 66 | { 67 | return new Var2<>(getter, setter); 68 | } 69 | 70 | /** 71 | * Create a simple Var with the initial value. 72 | *

73 | * The get() and set() methods of the Var are just like read/write a non-volatile field. 74 | *

75 | */ 76 | public static Var init(T initValue) 77 | { 78 | return new Var0<>(initValue); 79 | } 80 | 81 | /** 82 | * Create a Var with a validator. 83 | *

84 | * The set(value) methods of the Var will first call `validator.accept(value)`; 85 | * if the validator throws RuntimeException, set(value) throws the same exception. 86 | * Other than that, 87 | * the get() and set() methods are just like read/write a non-volatile field. 88 | *

89 | *

90 | * The initValue is also checked by the validator; 91 | * if the validator throws RuntimeException, this method throws the same exception. 92 | *

93 | */ 94 | public static Var init(T initValue, Consumer validator) throws RuntimeException 95 | { 96 | return new Var1<>(validator, initValue); 97 | } 98 | 99 | 100 | // for volatile semantics, we may provide volatile_xxx(...) methods in future. 101 | // for now, app can DIY by using of(getter, setter) with volatile getter/setter 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/bayou/util/Var0.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | class Var0 extends VarAbs 4 | { 5 | T value; 6 | 7 | Var0(T value) 8 | { 9 | this.value = value; 10 | } 11 | 12 | @Override 13 | public T get() 14 | { 15 | return value; 16 | } 17 | 18 | @Override 19 | public T set(T value) 20 | { 21 | this.value = value; 22 | return value; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/bayou/util/Var1.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.Supplier; 5 | 6 | class Var1 extends VarAbs 7 | { 8 | final Consumer validator; 9 | T value; 10 | 11 | Var1(Consumer validator, T value) 12 | { 13 | this.validator = validator; 14 | set(value); 15 | } 16 | 17 | @Override 18 | public T get() 19 | { 20 | return value; 21 | } 22 | 23 | @Override 24 | public T set(T value) 25 | { 26 | validator.accept(value); // may throw 27 | this.value = value; 28 | return value; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/bayou/util/Var2.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.Supplier; 5 | 6 | class Var2 extends VarAbs 7 | { 8 | final Supplier getter; 9 | final Consumer setter; 10 | 11 | Var2(Supplier getter, Consumer setter) 12 | { 13 | this.getter = getter; 14 | this.setter = setter; 15 | } 16 | 17 | @Override 18 | public T get() 19 | { 20 | return getter.get(); 21 | } 22 | 23 | @Override 24 | public T set(T value) 25 | { 26 | setter.accept(value); // may throw 27 | return value; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/bayou/util/VarAbs.java: -------------------------------------------------------------------------------- 1 | package bayou.util; 2 | 3 | import java.util.Objects; 4 | 5 | abstract class VarAbs implements Var 6 | { 7 | @Override 8 | public int hashCode() 9 | { 10 | return Objects.hashCode(get()); 11 | } 12 | 13 | @Override 14 | public boolean equals(Object obj) 15 | { 16 | return (obj instanceof Var) && Objects.equals(this.get(), ((Var)obj).get()); 17 | } 18 | 19 | @Override 20 | public String toString() 21 | { 22 | return String.valueOf(get()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/bayou/util/function/BiFunctionX.java: -------------------------------------------------------------------------------- 1 | package bayou.util.function; 2 | 3 | import java.util.function.*; 4 | /** 5 | * Like {@link BiFunction}, 6 | * except checked exceptions may be thrown. 7 | */ 8 | public interface BiFunctionX 9 | { 10 | /** 11 | * Like {@link BiFunction#apply}, except checked exceptions may be thrown. 12 | */ 13 | R apply(T t, U u) throws Exception; 14 | } 15 | -------------------------------------------------------------------------------- /src/bayou/util/function/Callable_Void.java: -------------------------------------------------------------------------------- 1 | package bayou.util.function; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | /** 6 | * An adapter of {@link Callable Callable<Void>} 7 | * for void-returning lambda expressions and method references. 8 | * 9 | *

10 | * Intended usage: 11 | * Some APIs accept {@code Callable}, for example 12 | *

13 | *
14 |  *     public static <T> void foo(Callable<T> action)
15 |  * 
16 | *

17 | * which, unfortunately, is not compatible with 18 | * void-returning lambda expressions and method references, for example 19 | *

20 | *
21 |  *    foo( ()->System.gc() );   // does NOT compile
22 |  *    foo( System::gc );        // does NOT compile
23 |  * 
24 | *

25 | * This adapter interface provides a workaround: 26 | *

27 | *
28 |  *    foo( (Callable_Void) ()->System.gc() )
29 |  *    foo( (Callable_Void) System::gc )
30 |  * 
31 | */ 32 | 33 | /* ------------------ 34 | 35 | can also be used to convert any value-return lambda to Callable 36 | foo( (Callable_Void) ()->System.nanoTime() ); 37 | foo( (Callable_Void) System::nanoTime ); 38 | but not sure how often users need it; so it's not documented 39 | 40 | 41 | maybe a simpler name like ToVoid? AsVoid? 42 | probably not used often, so a more explicit name is better. 43 | 44 | adapter for Func: Func_Void extends Func 45 | not as convenient, because of the type argument 46 | foo( (Func_Void) System.out::println ) 47 | it's better to provide another method 48 | foo2( System.out::println ) 49 | 50 | */ 51 | 52 | public interface Callable_Void extends Callable 53 | { 54 | /** 55 | * The single abstract method, which returns void (instead of Void). 56 | */ 57 | public void call_void() throws Exception; 58 | 59 | /** 60 | * Implements {@code Callable.call()}, 61 | * as: { call_void(); return null; } 62 | * 63 | * @return (Void)null 64 | * 65 | */ 66 | @Override 67 | default Void call() throws Exception 68 | { 69 | call_void(); 70 | return null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/bayou/util/function/ConsumerX.java: -------------------------------------------------------------------------------- 1 | package bayou.util.function; 2 | 3 | import java.util.function.*; 4 | /** 5 | * Like {@link Consumer}, 6 | * except checked exceptions may be thrown. 7 | */ 8 | public interface ConsumerX 9 | { 10 | /** 11 | * Like {@link Consumer#accept}, except checked exceptions may be thrown. 12 | */ 13 | public void accept(T t) throws Exception; 14 | 15 | 16 | /** 17 | * Similar to {@link Consumer#andThen(java.util.function.Consumer)}. 18 | */ 19 | public default ConsumerX then(ConsumerX that) 20 | { 21 | return (T t) -> 22 | { 23 | this.accept(t); 24 | that.accept(t); 25 | }; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/bayou/util/function/FunctionX.java: -------------------------------------------------------------------------------- 1 | package bayou.util.function; 2 | 3 | import java.util.function.*; 4 | /** 5 | * Like {@link Function}, 6 | * except checked exceptions may be thrown. 7 | */ 8 | public interface FunctionX 9 | { 10 | /** 11 | * Like {@link Function#apply}, except checked exceptions may be thrown. 12 | */ 13 | public R apply(T t) throws Exception; 14 | } 15 | -------------------------------------------------------------------------------- /src/bayou/util/function/RunnableX.java: -------------------------------------------------------------------------------- 1 | package bayou.util.function; 2 | 3 | 4 | /** 5 | * Like {@link Runnable}, 6 | * except checked exceptions may be thrown. 7 | */ 8 | public interface RunnableX 9 | { 10 | /** 11 | * Like {@link Runnable#run()}, except checked exceptions may be thrown. 12 | */ 13 | public void run() throws Exception; 14 | } 15 | -------------------------------------------------------------------------------- /src/bayou/util/function/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Some functional interfaces. 3 | */ 4 | package bayou.util.function; -------------------------------------------------------------------------------- /src/bayou/util/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Miscellaneous types and utilities. 3 | */ 4 | package bayou.util; -------------------------------------------------------------------------------- /src/bayou/websocket/ByteSource2WebSocketMessage.java: -------------------------------------------------------------------------------- 1 | package bayou.websocket; 2 | 3 | import bayou.async.Async; 4 | import bayou.bytes.ByteSource; 5 | 6 | import java.nio.ByteBuffer; 7 | 8 | class ByteSource2WebSocketMessage implements WebSocketMessage 9 | { 10 | final boolean isText; 11 | final ByteSource bs; 12 | 13 | public ByteSource2WebSocketMessage(boolean text, ByteSource bs) 14 | { 15 | isText = text; 16 | this.bs = bs; 17 | } 18 | 19 | @Override 20 | public boolean isText() 21 | { 22 | return isText; 23 | } 24 | 25 | @Override 26 | public Async read() 27 | { 28 | return bs.read(); 29 | } 30 | 31 | @Override 32 | public long skip(long n) throws IllegalArgumentException 33 | { 34 | return bs.skip(n); // may throw 35 | } 36 | 37 | @Override 38 | public Async close() 39 | { 40 | return bs.close(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/bayou/websocket/ThroughputMeter.java: -------------------------------------------------------------------------------- 1 | package bayou.websocket; 2 | 3 | import java.time.Duration; 4 | 5 | class ThroughputMeter 6 | { 7 | final long minThroughput; // bytes per second, >=0 8 | final long timeWindow; // ns 9 | 10 | public ThroughputMeter(long minThroughput, Duration timeWindow) 11 | { 12 | this.minThroughput = minThroughput; 13 | this.timeWindow = Math.max(timeWindow.toNanos(), 1000_000_000L); // at least one second 14 | } 15 | 16 | // remember only recent history 2*timeWindow 17 | long bytes1; 18 | long time1; 19 | long bytes2; 20 | long time2; 21 | 22 | long t0; // 0 if clock is paused, for assertions 23 | 24 | public void resumeClock() 25 | { 26 | assert t0==0; 27 | t0 = System.nanoTime(); 28 | } 29 | 30 | public boolean isPaused(){ return t0==0; } 31 | 32 | public void pauseClock() 33 | { 34 | assert t0!=0; 35 | time2 += (System.nanoTime()-t0); 36 | t0 = 0; 37 | } 38 | 39 | // return false if throughput is lower than min 40 | public boolean reportBytes(long bytes) 41 | { 42 | assert t0!=0; 43 | 44 | bytes2 += bytes; 45 | 46 | long now = System.nanoTime(); 47 | time2 += (now-t0); 48 | t0 = now; 49 | 50 | if(time2>timeWindow) 51 | { 52 | bytes1 = bytes2; 53 | time1 = time2; 54 | bytes2 = 0; 55 | time2 = 0; 56 | } 57 | 58 | long time = time1 + time2; 59 | if(time= minThroughput * time / 1000_000_000L; // possible overflow 63 | if(minThroughput==0) 64 | return true; 65 | 66 | return (bytes1+bytes2) * 1000L / minThroughput >= time / 1000_000L; 67 | // accurate to 1ms. 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/bayou/websocket/WebSocketClose.java: -------------------------------------------------------------------------------- 1 | package bayou.websocket; 2 | 3 | import bayou.util.End; 4 | 5 | /** 6 | * To indicate that a WebSocket inbound is gracefully closed. 7 | *

8 | * This is a control exception, a subtype of {@link End}. 9 | * It's used by {@link WebSocketChannel#readMessage()}. 10 | *

11 | *

12 | * Close-Frame is sent by a WebSocket endpoint to 13 | * gracefully close its outbound direction, 14 | * see RFC6455 §5.5.1. 15 | * The Close-Frame may contain code and reason for diagnosis. 16 | *

17 | * 18 | */ 19 | public class WebSocketClose extends End 20 | { 21 | final int code; 22 | final String reason; 23 | 24 | /** 25 | * Create a WebSocketClose instance. 26 | */ 27 | public WebSocketClose(int code, String reason) 28 | { 29 | super(""+code+": "+reason); 30 | 31 | this.code = code; 32 | this.reason = reason; 33 | } 34 | 35 | /** 36 | * The code given by the Close-Frame. If the Close-Frame contains no code, 1005 is the substitute value. 37 | *

38 | * See RFC6455 §7.4. 39 | *

40 | */ 41 | public int code() 42 | { 43 | return code; 44 | } 45 | 46 | /** 47 | * The reason given by the Close-Frame. If the Close-Frame contains no reason, "" is the substitute value. 48 | */ 49 | public String reason() 50 | { 51 | return reason; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/bayou/websocket/WebSocketException.java: -------------------------------------------------------------------------------- 1 | package bayou.websocket; 2 | 3 | /** 4 | * Exception in WebSocket communication. 5 | *

6 | * This exception usually indicates protocol violation by the peer. 7 | *

8 | */ 9 | // we only use it for protocol error from peer 10 | public class WebSocketException extends Exception 11 | { 12 | public WebSocketException(String msg) 13 | { 14 | super(msg); 15 | } 16 | 17 | public WebSocketException(String msg, Throwable cause) 18 | { 19 | super(msg, cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/bayou/websocket/WebSocketHandler.java: -------------------------------------------------------------------------------- 1 | package bayou.websocket; 2 | 3 | import bayou.async.Async; 4 | 5 | /** 6 | * Handles WebSocket handshakes. 7 | *

8 | * For each handshake request, the {@link #handle(WebSocketRequest)} action 9 | * generates a handshake response, either Reject or Accept. 10 | *

11 | *

12 | * For many applications, the handler can simply accept any request 13 | *

14 | *
15 |  *     WebSocketHandler handler = request -> WebSocketResponse.accept(this::handleChannel);
16 |  *
17 |  *     Async<Void> handleChannel(WebSocketChannel channel){ ... }
18 |  * 
19 | *

20 | * Note that by default, same-origin policy is enforced before the handler is invoked, 21 | * see {@link WebSocketServerConf#enforceSameOrigin(boolean)}. 22 | *

23 | */ 24 | public interface WebSocketHandler 25 | { 26 | /** 27 | * Handle a WebSocket handshake request. 28 | *

29 | * This action should generate either a {@link WebSocketResponse.Reject} 30 | * or a {@link WebSocketResponse.Accept}. 31 | *

32 | *

33 | * This method will be invoked by the server on a Fiber created for the underlying HTTP connection. 34 | *

35 | *

36 | * {@link bayou.http.CookieJar} can be used during this action to get/set cookies. 37 | * 38 | *

39 | */ 40 | public abstract Async handle(WebSocketRequest request); 41 | } 42 | -------------------------------------------------------------------------------- /src/bayou/websocket/WebSocketMessage.java: -------------------------------------------------------------------------------- 1 | package bayou.websocket; 2 | 3 | import _bayou._bytes._ErrorByteSource; 4 | import bayou.bytes.ByteSource; 5 | import bayou.bytes.SimpleByteSource; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.nio.CharBuffer; 9 | import java.nio.charset.CharsetEncoder; 10 | import java.nio.charset.StandardCharsets; 11 | 12 | /** 13 | * WebSocket message. 14 | *

15 | * A WebSocket message contains a sequence of bytes. 16 | * Note that WebSocketMessage is a subtype of {@link ByteSource}. 17 | *

18 | *

19 | * A WebSocket message is either 20 | * Text or Binary, 21 | * identified by the message header. 22 | * A Text message contains texts encoded in UTF-8 bytes. 23 | * A Binary message can contain arbitrary bytes. 24 | *

25 | *

26 | * See {@link ByteSource} methods for reading bytes/chars from the message. 27 | *

28 | *

29 | * See static methods in this interface for creating messages, 30 | * e.g. {@link #text(CharSequence)}, {@link #binary(byte[])}. 31 | *

32 | */ 33 | public interface WebSocketMessage extends ByteSource 34 | { 35 | /** 36 | * Whether this is a Text message. If false, this is a Binary message. 37 | */ 38 | abstract public boolean isText(); 39 | 40 | 41 | 42 | // -------------------------------------------------------------------------------------------- 43 | 44 | /** 45 | * Create a text message containing the chars. 46 | */ 47 | public static WebSocketMessage text(CharSequence chars) 48 | { 49 | try 50 | { 51 | CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); 52 | ByteBuffer bb = encoder.encode(CharBuffer.wrap(chars)); 53 | return text(new SimpleByteSource(bb)); 54 | } 55 | catch (Exception e) 56 | { 57 | return text(new _ErrorByteSource(e)); 58 | } 59 | } 60 | 61 | /** 62 | * Wrap a ByteSource as a text message. 63 | *

64 | * The bytes in `byteSource` must form a valid UTF-8 byte sequence. 65 | *

66 | */ 67 | public static WebSocketMessage text(ByteSource byteSource) 68 | { 69 | return new ByteSource2WebSocketMessage(true, byteSource); 70 | } 71 | 72 | 73 | /** 74 | * Create a binary message containing the bytes. 75 | */ 76 | public static WebSocketMessage binary(byte[] bytes) 77 | { 78 | return binary(new SimpleByteSource(bytes)); 79 | } 80 | 81 | /** 82 | * Wrap a ByteSource as a binary message. 83 | */ 84 | public static WebSocketMessage binary(ByteSource byteSource) 85 | { 86 | return new ByteSource2WebSocketMessage(false, byteSource); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/bayou/websocket/WsOp.java: -------------------------------------------------------------------------------- 1 | package bayou.websocket; 2 | 3 | import _bayou._str._HexUtil; 4 | 5 | class WsOp 6 | { 7 | final static int continue_ = 0x0; 8 | 9 | final static int text = 0x1; 10 | final static int binary = 0x2; 11 | 12 | final static int close = 0x8; 13 | final static int ping = 0x9; 14 | final static int pong = 0xA; 15 | 16 | static String[] strings = new String[16]; 17 | static 18 | { 19 | for(int i=0; i