├── siden-sandbox └── .gitignore ├── siden-example ├── assets │ ├── moge.txt │ ├── hello.jsx │ ├── react.mustache │ ├── console-polyfill.js │ ├── chat.html │ └── comments.jsx ├── src │ └── main │ │ ├── resources │ │ └── templates │ │ │ └── say │ │ │ └── hello.html │ │ └── java │ │ └── example │ │ ├── User.java │ │ ├── UseWebsocket.java │ │ ├── CollectMetrics.java │ │ ├── URLShortener.java │ │ ├── UseReactSSR.java │ │ ├── UseHandlebars.java │ │ ├── Main.java │ │ └── UseReactComplexSSR.java └── README.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── siden-core └── src │ ├── main │ ├── resources │ │ └── favicon.ico │ └── java │ │ └── ninja │ │ └── siden │ │ ├── internal │ │ ├── ExchangeState.java │ │ ├── BlockingRenderer.java │ │ ├── ConnectionCallback.java │ │ ├── MethodOverrideHandler.java │ │ ├── Core.java │ │ ├── FiltersHandler.java │ │ ├── SecurityHandler.java │ │ ├── ReceiveListenerAdapter.java │ │ ├── MIMEPredicate.java │ │ ├── PathPredicate.java │ │ ├── SidenSession.java │ │ ├── SidenCookie.java │ │ ├── SidenResponse.java │ │ └── LambdaWebSocketFactory.java │ │ ├── Stoppable.java │ │ ├── WebSocketFactory.java │ │ ├── jmx │ │ ├── SessionMXBean.java │ │ ├── RequestMXBean.java │ │ ├── WebSocketMXBean.java │ │ ├── RouteTracker.java │ │ ├── ObjectNames.java │ │ ├── RequestMetrics.java │ │ ├── RequestMeter.java │ │ ├── WebSocketTracker.java │ │ ├── SessionMetrics.java │ │ └── MetricsAppBuilder.java │ │ ├── FilterChain.java │ │ ├── Route.java │ │ ├── Filter.java │ │ ├── util │ │ ├── ExceptionalSupplier.java │ │ ├── ExceptionalConsumer.java │ │ ├── ExceptionalFunction.java │ │ ├── ExceptionalBiConsumer.java │ │ ├── Predicates.java │ │ ├── Suppress.java │ │ ├── LongAccumulators.java │ │ ├── Publisher.java │ │ ├── Using.java │ │ ├── ExactlyOnceCloseable.java │ │ └── Trial.java │ │ ├── RendererCustomizer.java │ │ ├── ExceptionalRoute.java │ │ ├── Session.java │ │ ├── RoutingCustomizer.java │ │ ├── AssetsCustomizer.java │ │ ├── def │ │ ├── SubAppDef.java │ │ ├── AppBuilder.java │ │ ├── AppContext.java │ │ ├── ErrorCodeRoutingDef.java │ │ ├── FilterDef.java │ │ ├── ExceptionalRoutingDef.java │ │ ├── WebSocketDef.java │ │ ├── AppDef.java │ │ ├── RoutingDef.java │ │ └── AssetDef.java │ │ ├── RendererRepository.java │ │ ├── WebSocket.java │ │ ├── AttributeContainer.java │ │ ├── WebSocketCustomizer.java │ │ ├── Cookie.java │ │ ├── Response.java │ │ ├── Renderer.java │ │ ├── SecurityHeaders.java │ │ ├── HttpMethod.java │ │ ├── Request.java │ │ └── Connection.java │ └── test │ └── java │ └── ninja │ └── siden │ ├── util │ ├── ExactlyOnceCloseableTest.java │ ├── PublisherTest.java │ ├── TrialTest.java │ └── LongAccumulatorsTest.java │ ├── jmx │ ├── ObjectNamesTest.java │ └── MetricsAppBuilderTest.java │ └── internal │ ├── SecurityHandlerTest.java │ ├── MIMEPredicateTest.java │ ├── FiltersHandlerTest.java │ ├── MethodOverrideHandlerTest.java │ ├── Testing.java │ ├── RendererSelectorTest.java │ ├── SidenRequestTest.java │ └── PathPredicateTest.java ├── .gitignore ├── wercker.yml ├── siden-react ├── src │ ├── test │ │ └── java │ │ │ └── ninja │ │ │ └── siden │ │ │ └── react │ │ │ └── JsEngineTest.java │ └── main │ │ └── java │ │ └── ninja │ │ └── siden │ │ └── react │ │ ├── JsEngine.java │ │ └── React.java └── README.md ├── README.md ├── TODO.md └── gradlew.bat /siden-sandbox/.gitignore: -------------------------------------------------------------------------------- 1 | /.generatedsrc/ 2 | -------------------------------------------------------------------------------- /siden-example/assets/moge.txt: -------------------------------------------------------------------------------- 1 | aaaa 2 | bbbb 3 | cccc 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichi/siden/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'siden' 2 | 3 | include 'siden-core', 'siden-react', 'siden-example', 'siden-sandbox' 4 | 5 | -------------------------------------------------------------------------------- /siden-core/src/main/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichi/siden/HEAD/siden-core/src/main/resources/favicon.ico -------------------------------------------------------------------------------- /siden-example/src/main/resources/templates/say/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello {{name}} !! 5 | 6 | 7 | -------------------------------------------------------------------------------- /siden-example/assets/hello.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var HelloMessage = React.createClass({ 3 | render: function() { 4 | return
Hello {this.props.name}
; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build 3 | */build 4 | gradle.properties 5 | 6 | */bin 7 | *.project 8 | *.classpath 9 | *.settings 10 | *.factorypath 11 | *.generated 12 | 13 | .idea 14 | *.ipr 15 | *.iws 16 | *.iml 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Sep 16 15:02:34 JST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-bin.zip 7 | -------------------------------------------------------------------------------- /siden-example/assets/react.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{& rendered}} 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: taichi/java8-oracle@0.0.1 2 | build: 3 | steps: 4 | - script: 5 | name: chmod 6 | code: chmod +x ./gradlew 7 | - script: 8 | name: show env 9 | code: ./gradlew --project-cache-dir=$WERCKER_CACHE_DIR/.gradle -v 10 | - script: 11 | name: run test 12 | code: ./gradlew --project-cache-dir=$WERCKER_CACHE_DIR/.gradle --full-stacktrace build 13 | -------------------------------------------------------------------------------- /siden-example/README.md: -------------------------------------------------------------------------------- 1 | # Siden Examples 2 | 3 | TBD 4 | 5 | see. https://github.com/taichi/siden/blob/master/siden-example/src/main/java/example/Main.java 6 | 7 | ## Request 8 | 9 | ### Session 10 | 11 | ## Filter 12 | 13 | ## Response 14 | 15 | ## Rendering 16 | 17 | ### Handlebars.java 18 | 19 | ### Boon JSON 20 | 21 | ### Jackson 22 | 23 | ## Error handling 24 | 25 | ### Exception 26 | 27 | ### Response code 28 | 29 | ## Compose Application 30 | 31 | ## Configurations 32 | 33 | ### Siden 34 | 35 | #### Turn off Response Headers for Security 36 | 37 | ### Undertow 38 | 39 | #### Use SSL 40 | -------------------------------------------------------------------------------- /siden-example/assets/console-polyfill.js: -------------------------------------------------------------------------------- 1 | // Console-polyfill. MIT license. 2 | // https://github.com/paulmillr/console-polyfill 3 | // Make it safe to do console.log() always. 4 | (function(con) { 5 | 'use strict'; 6 | var prop, method; 7 | var empty = {}; 8 | var dummy = function() {}; 9 | var properties = 'memory'.split(','); 10 | var methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' + 11 | 'groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,' + 12 | 'show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn').split(','); 13 | while (prop = properties.pop()) con[prop] = con[prop] || empty; 14 | while (method = methods.pop()) con[method] = con[method] || dummy; 15 | })(this.console = this.console || {}); // Using `this` for web workers. 16 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/ExchangeState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | enum ExchangeState { 22 | Rendered, Redirected; 23 | } 24 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/Stoppable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface Stoppable { 22 | 23 | void stop(); 24 | 25 | void addShutdownHook(); 26 | } 27 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/WebSocketFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface WebSocketFactory { 22 | 23 | WebSocket create(Connection connection); 24 | } 25 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/SessionMXBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface SessionMXBean { 22 | 23 | SessionMetrics getMetrics(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/FilterChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | @FunctionalInterface 22 | public interface FilterChain { 23 | 24 | Object next() throws Exception; 25 | } 26 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/RequestMXBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface RequestMXBean { 22 | 23 | RequestMetrics getMetrics(); 24 | 25 | void reset(); 26 | } 27 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/Route.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | 19 | /** 20 | * @author taichi 21 | */ 22 | @FunctionalInterface 23 | public interface Route { 24 | 25 | Object handle(Request request, Response response) throws Exception; 26 | } 27 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/Filter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | @FunctionalInterface 22 | public interface Filter { 23 | 24 | void filter(Request req, Response res, FilterChain chain) throws Exception; 25 | } 26 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/ExceptionalSupplier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | @FunctionalInterface 22 | public interface ExceptionalSupplier { 23 | 24 | T get() throws EX; 25 | } 26 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/RendererCustomizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface RendererCustomizer> { 22 | 23 | T render(Renderer renderer); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/ExceptionalConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | @FunctionalInterface 22 | public interface ExceptionalConsumer { 23 | 24 | void accept(T t) throws EX; 25 | } 26 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/ExceptionalFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | @FunctionalInterface 22 | public interface ExceptionalFunction { 23 | 24 | R apply(T t) throws EX; 25 | } 26 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/ExceptionalBiConsumer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | @FunctionalInterface 22 | public interface ExceptionalBiConsumer { 23 | 24 | void accept(T t, U u) throws EX; 25 | } 26 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/ExceptionalRoute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | @FunctionalInterface 22 | public interface ExceptionalRoute { 23 | 24 | Object handle(EX ex, Request request, Response response); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /siden-example/src/main/java/example/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package example; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public class User { 22 | 23 | String name; 24 | 25 | public User(String name) { 26 | this.name = name; 27 | } 28 | 29 | public String getName() { 30 | return this.name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/Session.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface Session extends AttributeContainer { 22 | 23 | String id(); 24 | 25 | void invalidate(); 26 | 27 | Session regenerate(); 28 | 29 | io.undertow.server.session.Session raw(); 30 | } 31 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/Predicates.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | import java.util.function.Predicate; 19 | 20 | /** 21 | * @author taichi 22 | */ 23 | public class Predicates { 24 | 25 | public static Predicate not(Predicate predicate) { 26 | return predicate.negate(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/Suppress.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface Suppress { 22 | 23 | static T get(ExceptionalSupplier supplier) { 24 | try { 25 | return supplier.get(); 26 | } catch (RuntimeException e) { 27 | throw e; 28 | } catch (Exception e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/RoutingCustomizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import java.util.function.Predicate; 19 | 20 | /** 21 | * @author taichi 22 | */ 23 | public interface RoutingCustomizer extends 24 | RendererCustomizer { 25 | 26 | RoutingCustomizer type(String type); 27 | 28 | RoutingCustomizer accept(String type); 29 | 30 | RoutingCustomizer match(Predicate fn); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/AssetsCustomizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface AssetsCustomizer { 22 | 23 | AssetsCustomizer cacheTime(Integer time); 24 | 25 | AssetsCustomizer directoryListing(boolean is); 26 | 27 | AssetsCustomizer welcomeFiles(String... files); 28 | 29 | // AssetsCustomizer etag(Boolean is); 30 | 31 | AssetsCustomizer from(ClassLoader loader); 32 | } 33 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/WebSocketMXBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface WebSocketMXBean { 22 | 23 | RequestMetrics getOnConnect(); 24 | 25 | RequestMetrics getOnText(); 26 | 27 | RequestMetrics getOnBinary(); 28 | 29 | RequestMetrics getOnPong(); 30 | 31 | RequestMetrics getOnPing(); 32 | 33 | RequestMetrics getOnClose(); 34 | 35 | void reset(); 36 | } 37 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/SubAppDef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | 19 | /** 20 | * @author taichi 21 | */ 22 | public class SubAppDef { 23 | 24 | final String prefix; 25 | 26 | final AppDef app; 27 | 28 | public SubAppDef(String prefix, AppDef app) { 29 | super(); 30 | this.prefix = prefix; 31 | this.app = app; 32 | } 33 | 34 | public String prefix() { 35 | return this.prefix; 36 | } 37 | 38 | public AppDef app() { 39 | return this.app; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/LongAccumulators.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | import java.util.concurrent.atomic.LongAccumulator; 19 | 20 | /** 21 | * @author taichi 22 | */ 23 | public interface LongAccumulators { 24 | 25 | static LongAccumulator max() { 26 | return new LongAccumulator((x, y) -> x < y ? y : x, 0); 27 | } 28 | 29 | static LongAccumulator min() { 30 | return new LongAccumulator( 31 | (x, y) -> y < x || (x < 0 && -1 < y) ? y : x, -1); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /siden-example/src/main/java/example/UseWebsocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package example; 17 | 18 | import ninja.siden.App; 19 | 20 | /** 21 | * @author taichi 22 | */ 23 | public class UseWebsocket { 24 | 25 | public static void main(String[] args) { 26 | App app = new App(); 27 | 28 | app.get("/", (q, s) -> new java.io.File("assets/chat.html")); 29 | 30 | app.websocket("/ws").onText( 31 | (con, txt) -> con.peers().forEach(c -> c.send(txt))); 32 | 33 | app.listen(8181).addShutdownHook(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/util/ExactlyOnceCloseableTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import org.junit.Test; 21 | 22 | /** 23 | * @author taichi 24 | */ 25 | public class ExactlyOnceCloseableTest { 26 | 27 | @Test 28 | public void close() { 29 | int[] counter = { 0 }; 30 | ExactlyOnceCloseable c = ExactlyOnceCloseable.wrap(() -> counter[0]++); 31 | c.close(); 32 | c.close(); 33 | assertEquals(1, counter[0]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/RendererRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | @FunctionalInterface 22 | public interface RendererRepository { 23 | 24 | static RendererRepository EMPTY = new RendererRepository() { 25 | @Override 26 | public Renderer find(String path) { 27 | throw new IllegalStateException( 28 | "RendererRepository is not configured. see. " 29 | + Config.class.getName() + "#" 30 | + Config.RENDERER_REPOSITORY.getName()); 31 | } 32 | }; 33 | 34 | Renderer find(String path); 35 | } 36 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/WebSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | /** 21 | * @author taichi 22 | */ 23 | public interface WebSocket { 24 | 25 | default void onConnect(Connection connection) throws Exception { 26 | } 27 | 28 | default void onText(String payload) throws Exception { 29 | } 30 | 31 | default void onBinary(ByteBuffer[] payload) throws Exception { 32 | } 33 | 34 | default void onPong(ByteBuffer[] payload) throws Exception { 35 | } 36 | 37 | default void onPing(ByteBuffer[] payload) throws Exception { 38 | } 39 | 40 | default void onClose(ByteBuffer[] payload) throws Exception { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /siden-example/src/main/java/example/CollectMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package example; 17 | 18 | import ninja.siden.App; 19 | import ninja.siden.Config; 20 | 21 | /** 22 | * @author taichi 23 | */ 24 | public class CollectMetrics { 25 | 26 | public static void main(String[] args) { 27 | // development environments don't need metrics. 28 | App app = App.configure(b -> b.set(Config.ENV, "stable")); 29 | 30 | app.get("/", (req, res) -> "hello"); 31 | 32 | App sub = new App(); 33 | sub.get("/hoi", (req, res) -> "HOIHOI"); 34 | sub.websocket("/ws").onText((c, s) -> c.send(s)); 35 | 36 | app.use("/aaa", sub); 37 | app.use("/bbb", sub); 38 | 39 | app.listen().addShutdownHook(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/AppBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | import io.undertow.server.HttpHandler; 19 | import ninja.siden.App; 20 | 21 | /** 22 | * @author taichi 23 | */ 24 | public interface AppBuilder { 25 | 26 | void begin(); 27 | 28 | void apply(AppContext context, AssetDef def); 29 | 30 | void apply(AppContext context, RoutingDef def); 31 | 32 | void apply(AppContext context, ErrorCodeRoutingDef def); 33 | 34 | void apply(AppContext context, ExceptionalRoutingDef def); 35 | 36 | void apply(AppContext context, SubAppDef def); 37 | 38 | void apply(AppContext context, WebSocketDef def); 39 | 40 | void apply(AppContext context, FilterDef def); 41 | 42 | HttpHandler end(App root); 43 | } 44 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/AttributeContainer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import java.util.Optional; 19 | 20 | /** 21 | * @author taichi 22 | */ 23 | public interface AttributeContainer extends Iterable { 24 | 25 | /** 26 | * @param key 27 | * attribute name 28 | * @param newone 29 | * new attribute 30 | * @return existing value 31 | */ 32 | Optional attr(String key, T newone); 33 | 34 | Optional attr(String key); 35 | 36 | /** 37 | * @param key 38 | * @return existing value 39 | */ 40 | Optional remove(String key); 41 | 42 | interface Attr { 43 | String name(); 44 | 45 | T value(); 46 | 47 | T remove(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/AppContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | import ninja.siden.App; 19 | 20 | /** 21 | * @author taichi 22 | */ 23 | public class AppContext { 24 | 25 | final App root; 26 | 27 | AppDef app; 28 | 29 | String prefix = ""; 30 | 31 | public AppContext(App root) { 32 | this.root = root; 33 | } 34 | 35 | public AppContext(AppContext parent, SubAppDef sam) { 36 | this.root = parent.root(); 37 | this.app = sam.app(); 38 | this.prefix = parent.prefix() + sam.prefix(); 39 | } 40 | 41 | public App root() { 42 | return this.root; 43 | } 44 | 45 | public AppDef app() { 46 | return this.app; 47 | } 48 | 49 | public String prefix() { 50 | return this.prefix; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/Publisher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collections; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | import java.util.function.Consumer; 23 | 24 | /** 25 | * @author taichi 26 | */ 27 | public class Publisher { 28 | 29 | List> listeners = Collections 30 | .synchronizedList(new ArrayList<>()); 31 | 32 | public void on(Consumer fn) { 33 | this.listeners.add(fn); 34 | } 35 | 36 | public void off(Consumer fn) { 37 | this.listeners.remove(fn); 38 | } 39 | 40 | public void post(E event) { 41 | for (Iterator> i = this.listeners.iterator(); i.hasNext();) { 42 | try { 43 | i.next().accept(event); 44 | } finally { 45 | i.remove(); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /siden-react/src/test/java/ninja/siden/react/JsEngineTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.react; 17 | 18 | import static org.junit.Assert.*; 19 | 20 | import java.util.Collections; 21 | 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | 25 | /** 26 | * @author taichi 27 | */ 28 | public class JsEngineTest { 29 | 30 | JsEngine target; 31 | 32 | @Before 33 | public void setUp() { 34 | this.target = new JsEngine(); 35 | } 36 | 37 | @Test 38 | public void testContainGlobal() { 39 | this.target.initialize(Collections.emptyList()); 40 | assertNotNull(this.target.eval("global")); 41 | } 42 | 43 | @Test 44 | public void testEvalSeparately() throws Exception { 45 | this.target.initialize(Collections.emptyList()); 46 | this.target.eval("var hoge = 10"); 47 | assertNull(this.target.eval("this['hoge']")); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/RouteTracker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | import ninja.siden.Request; 19 | import ninja.siden.Response; 20 | import ninja.siden.Route; 21 | 22 | /** 23 | * @author taichi 24 | */ 25 | public class RouteTracker implements Route, RequestMXBean { 26 | 27 | Route original; 28 | RequestMeter totalResult = new RequestMeter(); 29 | 30 | public RouteTracker(Route original) { 31 | this.original = original; 32 | } 33 | 34 | @Override 35 | public Object handle(Request request, Response response) throws Exception { 36 | return totalResult.apply(m -> original.handle(request, response)); 37 | } 38 | 39 | @Override 40 | public void reset() { 41 | this.totalResult.reset(); 42 | } 43 | 44 | @Override 45 | public RequestMetrics getMetrics() { 46 | return this.totalResult.toMetrics(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/BlockingRenderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.server.HttpServerExchange; 19 | 20 | import java.io.IOException; 21 | 22 | import ninja.siden.Renderer; 23 | 24 | /** 25 | * @author taichi 26 | */ 27 | public class BlockingRenderer implements Renderer { 28 | 29 | final Renderer renderer; 30 | 31 | public BlockingRenderer(Renderer renderer) { 32 | super(); 33 | this.renderer = renderer; 34 | } 35 | 36 | @Override 37 | public void render(T model, HttpServerExchange sink) throws IOException { 38 | if (sink.isBlocking() == false) { 39 | sink.startBlocking(); 40 | } 41 | if (sink.isInIoThread()) { 42 | sink.dispatch(exchange -> { 43 | renderer.render(model, exchange); 44 | }); 45 | } else { 46 | renderer.render(model, sink); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /siden-example/src/main/java/example/URLShortener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package example; 17 | 18 | import java.util.Optional; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | 21 | import ninja.siden.App; 22 | 23 | /** 24 | * @author taichi 25 | */ 26 | public class URLShortener { 27 | 28 | public static void main(String[] args) { 29 | App app = new App(); 30 | ConcurrentHashMap map = new ConcurrentHashMap<>(); 31 | app.post("/", (req, res) -> { 32 | Optional opt = req.body(); 33 | return opt.map(s -> { 34 | String k = Integer.toHexString(s.hashCode()); 35 | map.put(k, s); 36 | return String.format("http://%s/%s", 37 | req.raw().getHostAndPort(), k); 38 | }); 39 | }); 40 | 41 | app.get("/:k", (req, res) -> req.params("k").map(key -> map.get(key)) 42 | .map(res::redirect).orElse(404)); 43 | 44 | app.listen(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/Using.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | /** 19 | * @author taichi 20 | */ 21 | public interface Using { 22 | 23 | static R transform( 24 | ExceptionalSupplier supplier, 25 | ExceptionalFunction transformer) { 26 | try (IO t = supplier.get()) { 27 | return transformer.apply(t); 28 | } catch (RuntimeException e) { 29 | throw e; 30 | } catch (Exception e) { 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | 35 | static void consume( 36 | ExceptionalSupplier supplier, 37 | ExceptionalConsumer consumer) { 38 | try (IO t = supplier.get()) { 39 | consumer.accept(t); 40 | } catch (RuntimeException e) { 41 | throw e; 42 | } catch (Exception e) { 43 | throw new RuntimeException(e); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/ErrorCodeRoutingDef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | import ninja.siden.Renderer; 19 | import ninja.siden.RendererCustomizer; 20 | import ninja.siden.Route; 21 | 22 | /** 23 | * @author taichi 24 | */ 25 | public class ErrorCodeRoutingDef implements 26 | RendererCustomizer { 27 | 28 | final int code; 29 | final Route route; 30 | Renderer renderer; 31 | 32 | public ErrorCodeRoutingDef(int code, Route route) { 33 | super(); 34 | this.code = code; 35 | this.route = route; 36 | } 37 | 38 | @Override 39 | public ErrorCodeRoutingDef render(Renderer renderer) { 40 | this.renderer = renderer; 41 | return this; 42 | } 43 | 44 | public int code() { 45 | return this.code; 46 | } 47 | 48 | public Route route() { 49 | return this.route; 50 | } 51 | 52 | public Renderer renderer() { 53 | return this.renderer; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/WebSocketCustomizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | import ninja.siden.util.ExceptionalBiConsumer; 21 | import ninja.siden.util.ExceptionalConsumer; 22 | 23 | /** 24 | * @author taichi 25 | */ 26 | public interface WebSocketCustomizer { 27 | 28 | WebSocketCustomizer onConnect(ExceptionalConsumer fn); 29 | 30 | WebSocketCustomizer onText( 31 | ExceptionalBiConsumer fn); 32 | 33 | WebSocketCustomizer onBinary( 34 | ExceptionalBiConsumer fn); 35 | 36 | WebSocketCustomizer onPong( 37 | ExceptionalBiConsumer fn); 38 | 39 | WebSocketCustomizer onPing( 40 | ExceptionalBiConsumer fn); 41 | 42 | WebSocketCustomizer onClose( 43 | ExceptionalBiConsumer fn); 44 | } 45 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/jmx/ObjectNamesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | import javax.management.ObjectName; 24 | 25 | import ninja.siden.jmx.ObjectNames; 26 | 27 | import org.junit.Test; 28 | 29 | /** 30 | * @author taichi 31 | */ 32 | public class ObjectNamesTest { 33 | 34 | @Test 35 | public void to() throws Exception { 36 | ObjectName name = ObjectNames.to("aaa.bbb:type=Z"); 37 | assertEquals("aaa.bbb", name.getDomain()); 38 | assertEquals("Z", name.getKeyProperty("type")); 39 | } 40 | 41 | @Test 42 | public void withMap() throws Exception { 43 | List list = Arrays.asList("type", "Z", "aaa", "bbb", "ccc", 44 | "ddd", "bbb", "zzz"); 45 | ObjectName name = ObjectNames.to("aaa.bbb", list); 46 | assertEquals("aaa.bbb:type=Z,aaa=bbb,ccc=ddd,bbb=zzz", name.toString()); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/Cookie.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import java.util.Date; 19 | 20 | /** 21 | * @author taichi 22 | */ 23 | public interface Cookie { 24 | 25 | String name(); 26 | 27 | String value(); 28 | 29 | Cookie value(final String value); 30 | 31 | String path(); 32 | 33 | Cookie path(final String path); 34 | 35 | String domain(); 36 | 37 | Cookie domain(final String domain); 38 | 39 | Integer maxAge(); 40 | 41 | Cookie maxAge(final Integer maxAge); 42 | 43 | boolean discard(); 44 | 45 | Cookie discard(final boolean discard); 46 | 47 | boolean secure(); 48 | 49 | Cookie secure(final boolean secure); 50 | 51 | int version(); 52 | 53 | Cookie version(final int version); 54 | 55 | boolean httpOnly(); 56 | 57 | Cookie httpOnly(final boolean httpOnly); 58 | 59 | Date expires(); 60 | 61 | Cookie expires(final Date expires); 62 | 63 | String comment(); 64 | 65 | Cookie comment(final String comment); 66 | } 67 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/FilterDef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | import io.undertow.predicate.Predicate; 19 | import io.undertow.server.HttpServerExchange; 20 | 21 | import java.util.Objects; 22 | 23 | import ninja.siden.Filter; 24 | import ninja.siden.FilterChain; 25 | import ninja.siden.Request; 26 | import ninja.siden.Response; 27 | 28 | /** 29 | * @author taichi 30 | */ 31 | public class FilterDef implements Predicate, Filter { 32 | 33 | final Predicate predicate; 34 | 35 | final Filter filter; 36 | 37 | public FilterDef(Predicate predicate, Filter filter) { 38 | this.predicate = Objects.requireNonNull(predicate); 39 | this.filter = Objects.requireNonNull(filter); 40 | } 41 | 42 | @Override 43 | public boolean resolve(HttpServerExchange value) { 44 | return this.predicate.resolve(value); 45 | } 46 | 47 | @Override 48 | public void filter(Request req, Response res, FilterChain chain) 49 | throws Exception { 50 | this.filter.filter(req, res, chain); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/ExceptionalRoutingDef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | import ninja.siden.ExceptionalRoute; 19 | import ninja.siden.Renderer; 20 | import ninja.siden.RendererCustomizer; 21 | 22 | /** 23 | * @author taichi 24 | */ 25 | public class ExceptionalRoutingDef implements 26 | RendererCustomizer> { 27 | 28 | Class type; 29 | ExceptionalRoute route; 30 | Renderer renderer; 31 | 32 | public ExceptionalRoutingDef(Class type, ExceptionalRoute route) { 33 | super(); 34 | this.type = type; 35 | this.route = route; 36 | } 37 | 38 | @Override 39 | public ExceptionalRoutingDef render(Renderer renderer) { 40 | this.renderer = renderer; 41 | return this; 42 | } 43 | 44 | public Class type() { 45 | return this.type; 46 | } 47 | 48 | public ExceptionalRoute route() { 49 | return this.route; 50 | } 51 | 52 | public Renderer renderer() { 53 | return this.renderer; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/ObjectNames.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | import java.util.Iterator; 19 | import java.util.List; 20 | 21 | import javax.management.MalformedObjectNameException; 22 | import javax.management.ObjectName; 23 | 24 | /** 25 | * @author taichi 26 | */ 27 | public interface ObjectNames { 28 | 29 | static ObjectName to(CharSequence name) { 30 | try { 31 | return new ObjectName(name.toString()); 32 | } catch (MalformedObjectNameException e) { 33 | throw new IllegalArgumentException(e); 34 | } 35 | } 36 | 37 | static ObjectName to(CharSequence domain, List props) { 38 | if (props.size() % 2 != 0) { 39 | throw new IllegalArgumentException(); 40 | } 41 | StringBuilder stb = new StringBuilder(domain); 42 | stb.append(":"); 43 | for (Iterator i = props.iterator(); i.hasNext();) { 44 | stb.append(i.next()); 45 | stb.append('='); 46 | stb.append(i.next()); 47 | if (i.hasNext()) { 48 | stb.append(','); 49 | } 50 | } 51 | return to(stb); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/util/PublisherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import java.util.function.Consumer; 21 | 22 | import ninja.siden.util.Publisher; 23 | 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | 27 | /** 28 | * @author taichi 29 | */ 30 | public class PublisherTest { 31 | 32 | Publisher target; 33 | 34 | @Before 35 | public void setUp() throws Exception { 36 | this.target = new Publisher<>(); 37 | } 38 | 39 | @Test 40 | public void on() throws Exception { 41 | String[] called = { null }; 42 | target.on(s -> called[0] = s); 43 | target.post("aaa"); 44 | assertEquals("aaa", called[0]); 45 | target.post("bbb"); 46 | assertEquals("aaa", called[0]); 47 | } 48 | 49 | @Test 50 | public void off() throws Exception { 51 | String[] called = { "aaa" }; 52 | Consumer fn = s -> called[0] = s; 53 | target.on(fn); 54 | target.off(fn); 55 | target.post("bbb"); 56 | assertEquals("aaa", called[0]); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/util/TrialTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.util.Optional; 23 | 24 | import org.junit.Test; 25 | 26 | /** 27 | * @author taichi 28 | */ 29 | public class TrialTest { 30 | 31 | static File ok(String s) throws IOException { 32 | return new File(s); 33 | } 34 | 35 | static File ng(String s) throws IOException { 36 | throw new IOException(s); 37 | } 38 | 39 | @Test 40 | public void success() throws Exception { 41 | int ret = Optional.of("aaa").map(Trial.of(TrialTest::ok)) 42 | . map(t -> t.either(f -> 200, ioex -> 400)) 43 | .map(i -> i + 10).get(); 44 | assertEquals(210, ret); 45 | } 46 | 47 | @Test 48 | public void failed() throws Exception { 49 | int ret = Optional.of("aaa").map(Trial.of(TrialTest::ng)) 50 | . map(t -> t.either(f -> 200, ioex -> 400)) 51 | .map(i -> i + 11).get(); 52 | 53 | assertEquals(411, ret); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/Response.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import io.undertow.server.HttpServerExchange; 19 | 20 | import java.util.Map; 21 | 22 | /** 23 | * @author taichi 24 | */ 25 | public interface Response { 26 | 27 | Response status(int code); 28 | 29 | Response header(String name, String... values); 30 | 31 | /** 32 | * set RFC1123 date pattern to Response header. 33 | * 34 | * @param name 35 | * @param date 36 | * @return this 37 | */ 38 | Response header(String name, long date); 39 | 40 | Response headers(Map headers); 41 | 42 | Cookie cookie(String name, String value); 43 | 44 | /** 45 | * @param name 46 | * @return existing value 47 | */ 48 | Cookie removeCookie(String name); 49 | 50 | /** 51 | * @param contentType 52 | */ 53 | Response type(String contentType); 54 | 55 | Object redirect(String location); 56 | 57 | Object redirect(int code, String location); 58 | 59 | Object render(MODEL model, Renderer renderer); 60 | 61 | Object render(MODEL model, String template); 62 | 63 | HttpServerExchange raw(); 64 | } 65 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/ExactlyOnceCloseable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 19 | import java.util.logging.Level; 20 | import java.util.logging.Logger; 21 | 22 | /** 23 | * @author taichi 24 | */ 25 | public class ExactlyOnceCloseable implements AutoCloseable { 26 | 27 | static final AtomicReferenceFieldUpdater UPDATER = AtomicReferenceFieldUpdater 28 | .newUpdater(ExactlyOnceCloseable.class, AutoCloseable.class, 29 | "delegate"); 30 | 31 | volatile AutoCloseable delegate; 32 | 33 | public ExactlyOnceCloseable(AutoCloseable closeable) { 34 | this.delegate = closeable; 35 | } 36 | 37 | public static ExactlyOnceCloseable wrap(AutoCloseable c) { 38 | return new ExactlyOnceCloseable(c); 39 | } 40 | 41 | @Override 42 | public void close() { 43 | try { 44 | UPDATER.getAndUpdate(this, c -> () -> { 45 | }).close(); 46 | } catch (Exception ignore) { 47 | Logger.getLogger(ExactlyOnceCloseable.class.getName()).log( 48 | Level.FINER, ignore.getMessage(), ignore); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Siden 2 | tiny web application framework for Java SE 8. 3 | 4 | Siden focus on writing your application quickly and running server more faster. 5 | 6 | ## Getting Started 7 | 8 | ### Write simple java application 9 | 10 | ```java 11 | import ninja.siden.App; 12 | 13 | public class Main { 14 | public static void main(String[] args) { 15 | App app = new App(); 16 | app.get("/hello", (req, res) -> "Hello world"); 17 | app.listen(); 18 | } 19 | } 20 | ``` 21 | 22 | if you want to more examples, see [example/Main.java](https://github.com/taichi/siden/blob/master/siden-example/src/main/java/example/Main.java). 23 | 24 | ### Add dependency to your build.gradle 25 | 26 | ```groovy 27 | apply plugin: 'java' 28 | 29 | repositories.jcenter() 30 | 31 | dependencies { 32 | compile 'ninja.siden:siden-core:0.6.0' 33 | } 34 | 35 | sourceCompatibility = targetCompatibility = 1.8 36 | ``` 37 | 38 | ### Run and View 39 | 40 | http://localhost:8080/hello 41 | 42 | ## WebSocket Example 43 | 44 | ```java 45 | import java.nio.file.Paths; 46 | import ninja.siden.App; 47 | 48 | public class UseWebsocket { 49 | public static void main(String[] args) { 50 | App app = new App(); 51 | app.get("/", (q, s) -> Paths.get("assets/chat.html")); 52 | app.websocket("/ws").onText( 53 | (con, txt) -> con.peers().forEach(c -> c.send(txt))); 54 | app.listen(8181); 55 | } 56 | } 57 | ``` 58 | 59 | # License 60 | 61 | Apache License, Version 2.0 62 | 63 | # Inspired projects 64 | 65 | * http://expressjs.com/ 66 | * http://www.sinatrarb.com/ 67 | * http://www.sparkjava.com/ 68 | * http://flask.pocoo.org/ 69 | 70 | # Badges 71 | 72 | [![wercker status](https://app.wercker.com/status/de09957e13da7a18ae6cf3fbd67afc68/m "wercker status")](https://app.wercker.com/project/bykey/de09957e13da7a18ae6cf3fbd67afc68) 73 | -------------------------------------------------------------------------------- /siden-example/src/main/java/example/UseReactSSR.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package example; 17 | 18 | import java.nio.file.Paths; 19 | import java.util.Arrays; 20 | 21 | import ninja.siden.App; 22 | import ninja.siden.react.React; 23 | 24 | /** 25 | * React.js Server Side Rendering Example on JVM. 26 | * 27 | * @author taichi 28 | */ 29 | public class UseReactSSR { 30 | 31 | public static void main(String[] args) { 32 | // setup react server side rendering 33 | React rc = new React("HelloMessage", "content", Arrays.asList( 34 | // https://github.com/paulmillr/console-polyfill 35 | // Nashorn don't contain console object. 36 | Paths.get("assets", "console-polyfill.js"), 37 | // https://github.com/facebook/react 38 | Paths.get("assets", "react.js"), 39 | // npm install -g react-tools 40 | // jsx -x jsx assets build 41 | // siden-react don't support jsx compile. 42 | Paths.get("build", "hello.js"))); 43 | 44 | App app = new App(); 45 | app.get("/", (q, s) -> { 46 | // serialized json 47 | String props = "{\"name\":\"john\"}"; 48 | // server side rendering 49 | return "" + rc.toHtml(props) + ""; 50 | }).type("text/html"); 51 | app.listen().addShutdownHook(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/WebSocketDef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | import io.undertow.predicate.Predicate; 19 | import io.undertow.predicate.PredicatesHandler; 20 | import io.undertow.websockets.WebSocketProtocolHandshakeHandler; 21 | import ninja.siden.WebSocketFactory; 22 | import ninja.siden.internal.ConnectionCallback; 23 | 24 | import org.xnio.OptionMap; 25 | 26 | /** 27 | * @author taichi 28 | */ 29 | public class WebSocketDef { 30 | 31 | final String template; 32 | final Predicate predicate; 33 | final WebSocketFactory factory; 34 | 35 | public WebSocketDef(String template, Predicate predicate, 36 | WebSocketFactory factory) { 37 | super(); 38 | this.template = template; 39 | this.predicate = predicate; 40 | this.factory = factory; 41 | } 42 | 43 | public String template() { 44 | return this.template; 45 | } 46 | 47 | public Predicate predicate() { 48 | return this.predicate; 49 | } 50 | 51 | public WebSocketFactory factory() { 52 | return this.factory; 53 | } 54 | 55 | public void addTo(PredicatesHandler ph, OptionMap config) { 56 | ph.addPredicatedHandler(this.predicate(), 57 | next -> new WebSocketProtocolHandshakeHandler( 58 | new ConnectionCallback(this.factory()), next)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/internal/SecurityHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import static org.junit.Assert.assertNotNull; 19 | import io.undertow.server.HttpHandler; 20 | import io.undertow.server.HttpServerExchange; 21 | import io.undertow.util.HttpString; 22 | import mockit.integration.junit4.JMockit; 23 | import ninja.siden.Config; 24 | import ninja.siden.SecurityHeaders; 25 | 26 | import org.junit.Before; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | 30 | /** 31 | * @author taichi 32 | */ 33 | @RunWith(JMockit.class) 34 | public class SecurityHandlerTest { 35 | 36 | HttpServerExchange exchange; 37 | HttpHandler target; 38 | 39 | @Before 40 | public void setUp() { 41 | this.exchange = new HttpServerExchange(null); 42 | this.exchange.putAttachment(Core.CONFIG, Config.defaults().getMap()); 43 | 44 | this.target = new SecurityHandler(Testing.mustCall()); 45 | } 46 | 47 | void assertHeader(HttpString name) { 48 | assertNotNull(this.exchange.getResponseHeaders().get(name)); 49 | } 50 | 51 | @Test 52 | public void testHeaders() throws Exception { 53 | this.target.handleRequest(this.exchange); 54 | assertHeader(SecurityHeaders.FRAME_OPTIONS); 55 | assertHeader(SecurityHeaders.XSS_PROTECTION); 56 | assertHeader(SecurityHeaders.CONTENT_TYPE_OPTIONS); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/util/LongAccumulatorsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import java.util.concurrent.atomic.LongAccumulator; 21 | 22 | import org.junit.experimental.runners.Enclosed; 23 | import org.junit.experimental.theories.DataPoints; 24 | import org.junit.experimental.theories.Theories; 25 | import org.junit.experimental.theories.Theory; 26 | import org.junit.runner.RunWith; 27 | 28 | /** 29 | * @author taichi 30 | */ 31 | @RunWith(Enclosed.class) 32 | public class LongAccumulatorsTest { 33 | 34 | @RunWith(Theories.class) 35 | public static class Max { 36 | @DataPoints 37 | public static int[][] fixtures = { { 10, 11, 11 }, { 10, 9, 10 } }; 38 | 39 | @Theory 40 | public void test(int[] fixture) { 41 | LongAccumulator la = LongAccumulators.max(); 42 | la.accumulate(fixture[0]); 43 | la.accumulate(fixture[1]); 44 | assertEquals(fixture[2], la.get()); 45 | } 46 | } 47 | 48 | @RunWith(Theories.class) 49 | public static class Min { 50 | @DataPoints 51 | public static int[][] fixtures = { { 10, 11, 10 }, { 10, 9, 9 }, 52 | { -10, 7, 7 } }; 53 | 54 | @Theory 55 | public void test(int[] fixture) { 56 | LongAccumulator la = LongAccumulators.min(); 57 | la.accumulate(fixture[0]); 58 | la.accumulate(fixture[1]); 59 | assertEquals(fixture[2], la.get()); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | # Monitoring support 3 | * https://github.com/rhq-project/rhq 4 | * https://github.com/dropwizard/metrics 5 | * https://github.com/undertow-io/undertow/blob/master/core/src/main/java/io/undertow/server/handlers/MetricsHandler.java 6 | * https://github.com/CloudBees-community/wildfly-clickstack/blob/master/src/clickstack-resources/jboss-base-dir/configuration/wildfly-metrics.xml 7 | 8 | # Write more documents 9 | 10 | * make site on s3 11 | * define deployment pipeline. 12 | * more examples 13 | * javadoc 14 | 15 | # database integration 16 | 17 | * https://github.com/jOOQ/jOOQ 18 | * https://github.com/brianm/jdbi 19 | 20 | # don't work 21 | 22 | # template engine support 23 | ## Handlebars.java 24 | https://github.com/jknack/handlebars.java 25 | 26 | ## FreeMarker 27 | http://freemarker.org/ 28 | 29 | ## Thymeleaf 30 | http://www.thymeleaf.org/ 31 | 32 | ## Mustache 33 | https://github.com/spullara/mustache.java 34 | 35 | ## Jade 36 | https://github.com/neuland/jade4j 37 | 38 | 39 | ## NestedQuery support 40 | [Rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L104) や[qs](https://github.com/hapijs/qs)のようなnested queryはパラメータのキー表現とそれを取り出すときのコードの表現が一致しているから使い易いのであって、Javaでやるとどうしても全然違った表現になってしまう為、特に使い易くない。 41 | 42 | やるならJAX-RSのようにパラメータをオブジェクトにマッピングすべき。 43 | JSONからオブジェクトへのマッピングは便利なライブラリが沢山あるのであるからして、NestedQueryを使いたいケースは少ない気がする。 44 | 45 | 46 | 47 | ```java 48 | public class NestedQuery { 49 | 50 | Map kids = new HashMap<>(); 51 | 52 | String value = ""; 53 | 54 | List list = new ArrayList<>(); 55 | 56 | public NestedQuery get(String key) { 57 | return this.kids.get(key); 58 | } 59 | 60 | public String value() { 61 | return this.value; 62 | } 63 | 64 | public List list() { 65 | return Collections.unmodifiableList(this.list); 66 | } 67 | 68 | public static NestedQuery to(Map> params) { 69 | return to(params, Config.defaults().getMap()); 70 | } 71 | 72 | public static NestedQuery to(Map> params, 73 | OptionMap options) { 74 | return null; 75 | } 76 | } 77 | ``` 78 | 79 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/Renderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import io.undertow.server.HttpServerExchange; 19 | 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | import java.io.OutputStreamWriter; 23 | import java.io.Writer; 24 | 25 | import ninja.siden.internal.BlockingRenderer; 26 | import ninja.siden.internal.Core; 27 | 28 | import org.xnio.OptionMap; 29 | 30 | /** 31 | * @author taichi 32 | */ 33 | @FunctionalInterface 34 | public interface Renderer { 35 | 36 | void render(T model, HttpServerExchange sink) throws IOException; 37 | 38 | public static Renderer ofStream( 39 | OutputStreamConsumer fn) { 40 | return new BlockingRenderer((model, sink) -> fn.render(model, 41 | sink.getOutputStream())); 42 | } 43 | 44 | public static Renderer of(WriterConsumer fn) { 45 | return new BlockingRenderer((model, sink) -> { 46 | OptionMap config = sink.getAttachment(Core.CONFIG); 47 | Writer w = new OutputStreamWriter(sink.getOutputStream(), 48 | config.get(Config.CHARSET)); 49 | fn.render(model, w); 50 | w.flush(); 51 | }); 52 | } 53 | 54 | @FunctionalInterface 55 | public interface OutputStreamConsumer { 56 | void render(T model, OutputStream out) throws IOException; 57 | } 58 | 59 | @FunctionalInterface 60 | public interface WriterConsumer { 61 | void render(T model, Writer out) throws IOException; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/SecurityHeaders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import io.undertow.util.HttpString; 19 | 20 | /** 21 | * @author taichi 22 | * @see List_of_useful_HTTP_headers 24 | */ 25 | public interface SecurityHeaders { 26 | 27 | String REQUESTED_WITH_STRING = "X-Requested-With"; 28 | 29 | String STRICT_TRANSPORT_SECURITY_STRING = "Strict-Transport-Security"; 30 | String FRAME_OPTIONS_STRING = "X-Frame-Options"; 31 | String XSS_PROTECTION_STRING = "X-XSS-Protection"; 32 | String CONTENT_TYPE_OPTIONS_STRING = "X-Content-Type-Options"; 33 | String CONTENT_SECURITY_POLICY_STRING = "Content-Security-Policy"; 34 | String CONTENT_SECURITY_POLICY_REPORT_ONLY_STRING = "Content-Security-Policy-Report-Only"; 35 | 36 | HttpString REQUESTED_WITH = new HttpString(REQUESTED_WITH_STRING); 37 | 38 | HttpString STRICT_TRANSPORT_SECURITY = new HttpString( 39 | STRICT_TRANSPORT_SECURITY_STRING); 40 | HttpString FRAME_OPTIONS = new HttpString(FRAME_OPTIONS_STRING); 41 | HttpString XSS_PROTECTION = new HttpString(XSS_PROTECTION_STRING); 42 | HttpString CONTENT_TYPE_OPTIONS = new HttpString( 43 | CONTENT_TYPE_OPTIONS_STRING); 44 | HttpString CONTENT_SECURITY_POLICY = new HttpString( 45 | CONTENT_SECURITY_POLICY_STRING); 46 | HttpString CONTENT_SECURITY_REPORT_ONLY_POLICY = new HttpString( 47 | CONTENT_SECURITY_POLICY_REPORT_ONLY_STRING); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /siden-react/src/main/java/ninja/siden/react/JsEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.react; 17 | 18 | import java.nio.file.Files; 19 | import java.nio.file.Path; 20 | import java.util.List; 21 | 22 | import javax.script.ScriptContext; 23 | import javax.script.ScriptEngine; 24 | import javax.script.ScriptEngineManager; 25 | 26 | import ninja.siden.util.Suppress; 27 | import ninja.siden.util.Using; 28 | 29 | import org.jboss.logging.Logger; 30 | 31 | /** 32 | * @author taichi 33 | */ 34 | public class JsEngine { 35 | 36 | static final Logger LOG = Logger.getLogger(JsEngine.class); 37 | 38 | final ScriptEngineManager manager; 39 | 40 | public JsEngine() { 41 | manager = new ScriptEngineManager(); 42 | } 43 | 44 | ScriptEngine newEngine() { 45 | return manager.getEngineByExtension("js"); 46 | } 47 | 48 | public void initialize(List scripts) { 49 | ScriptEngine se = newEngine(); 50 | Suppress.get(() -> se.eval("var global = this;")); 51 | scripts.forEach(p -> eval(se, p)); 52 | this.manager.setBindings(se.getBindings(ScriptContext.ENGINE_SCOPE)); 53 | } 54 | 55 | public Object eval(String script) { 56 | LOG.debug(manager.getBindings().keySet()); 57 | ScriptEngine engine = newEngine(); 58 | return Suppress.get(() -> engine.eval(script)); 59 | } 60 | 61 | public Object eval(Path path) { 62 | LOG.debug(manager.getBindings().keySet()); 63 | return eval(newEngine(), path); 64 | } 65 | 66 | Object eval(ScriptEngine engine, Path path) { 67 | return Using.transform(() -> Files.newBufferedReader(path), 68 | engine::eval); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/ConnectionCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.UndertowLogger; 19 | import io.undertow.websockets.WebSocketConnectionCallback; 20 | import io.undertow.websockets.core.WebSocketChannel; 21 | import io.undertow.websockets.spi.WebSocketHttpExchange; 22 | 23 | import java.io.IOException; 24 | import java.util.Collections; 25 | import java.util.Set; 26 | import java.util.concurrent.ConcurrentHashMap; 27 | 28 | import ninja.siden.Connection; 29 | import ninja.siden.WebSocket; 30 | import ninja.siden.WebSocketFactory; 31 | 32 | import org.xnio.IoUtils; 33 | 34 | /** 35 | * @author taichi 36 | */ 37 | public class ConnectionCallback implements WebSocketConnectionCallback { 38 | 39 | final WebSocketFactory factory; 40 | final Set peers = Collections 41 | .newSetFromMap(new ConcurrentHashMap<>()); 42 | 43 | public ConnectionCallback(WebSocketFactory factory) { 44 | this.factory = factory; 45 | } 46 | 47 | @Override 48 | public void onConnect(WebSocketHttpExchange exchange, 49 | WebSocketChannel channel) { 50 | try { 51 | Connection connection = new SidenConnection(exchange, channel, 52 | peers); 53 | WebSocket socket = factory.create(connection); 54 | socket.onConnect(connection); 55 | channel.getReceiveSetter().set(new ReceiveListenerAdapter(socket)); 56 | channel.resumeReceives(); 57 | } catch (IOException e) { 58 | UndertowLogger.REQUEST_IO_LOGGER.ioException(e); 59 | IoUtils.safeClose(channel); 60 | } catch (Exception e) { 61 | IoUtils.safeClose(channel); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/MethodOverrideHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.server.HttpHandler; 19 | import io.undertow.server.HttpServerExchange; 20 | import io.undertow.server.handlers.form.FormData; 21 | import io.undertow.server.handlers.form.FormDataParser; 22 | import io.undertow.util.HttpString; 23 | import io.undertow.util.Methods; 24 | 25 | import java.util.Optional; 26 | 27 | import ninja.siden.HttpMethod; 28 | 29 | /** 30 | * @author taichi 31 | */ 32 | public class MethodOverrideHandler implements HttpHandler { 33 | 34 | static final HttpString HEADER = new HttpString("X-HTTP-Method-Override"); 35 | 36 | static final String FORM = "_method"; 37 | 38 | HttpHandler next; 39 | 40 | public MethodOverrideHandler(HttpHandler next) { 41 | this.next = next; 42 | } 43 | 44 | @Override 45 | public void handleRequest(HttpServerExchange exchange) throws Exception { 46 | if (Methods.POST.equals(exchange.getRequestMethod())) { 47 | String newMethod = exchange.getRequestHeaders().getFirst(HEADER); 48 | Optional opt = HttpMethod.find(newMethod); 49 | if (opt.isPresent()) { 50 | exchange.setRequestMethod(opt.get()); 51 | } else { 52 | FormData data = exchange 53 | .getAttachment(FormDataParser.FORM_DATA); 54 | if (data != null) { 55 | FormData.FormValue fv = data.getFirst(FORM); 56 | if (fv != null && fv.isFile() == false) { 57 | HttpMethod.find(fv.getValue()).map( 58 | exchange::setRequestMethod); 59 | } 60 | } 61 | } 62 | } 63 | this.next.handleRequest(exchange); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /siden-react/src/main/java/ninja/siden/react/React.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.react; 17 | 18 | import java.nio.file.Path; 19 | import java.util.List; 20 | 21 | /** 22 | * @author taichi 23 | */ 24 | public class React { 25 | 26 | final JsEngine engine = new JsEngine(); 27 | 28 | final String name; 29 | 30 | final String containerId; 31 | 32 | public React(String name, String containerId, List scripts) { 33 | super(); 34 | this.name = name; 35 | this.containerId = containerId; 36 | this.engine.initialize(scripts); 37 | } 38 | 39 | String makeScript(String encodedProps) { 40 | StringBuilder stb = new StringBuilder(); 41 | stb.append("React.renderToString("); 42 | appendInitializer(stb, encodedProps); 43 | stb.append(")"); 44 | return stb.toString(); 45 | } 46 | 47 | public StringBuilder toHtml(String encodedProps) { 48 | StringBuilder stb = new StringBuilder(); 49 | stb.append("
"); 52 | stb.append(this.engine.eval(makeScript(encodedProps))); 53 | stb.append("
"); 54 | return stb; 55 | } 56 | 57 | public StringBuilder toClientJs(String encodedProps) { 58 | StringBuilder stb = new StringBuilder(); 59 | stb.append("React.render("); 60 | appendInitializer(stb, encodedProps); 61 | stb.append(", document.getElementById("); 62 | stb.append("\""); 63 | stb.append(this.containerId); 64 | stb.append("\"));"); 65 | return stb; 66 | } 67 | 68 | void appendInitializer(StringBuilder a, String encodedProps) { 69 | a.append(this.name); 70 | a.append('('); 71 | a.append(encodedProps); 72 | a.append(')'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/HttpMethod.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import io.undertow.predicate.Predicate; 19 | import io.undertow.server.HttpServerExchange; 20 | import io.undertow.util.HttpString; 21 | import io.undertow.util.Methods; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.Optional; 26 | 27 | /** 28 | * @author taichi 29 | */ 30 | public enum HttpMethod implements Predicate { 31 | 32 | GET(Methods.GET), HEAD(Methods.HEAD), POST(Methods.POST), PUT(Methods.PUT), DELETE( 33 | Methods.DELETE), TRACE(Methods.TRACE), OPTIONS(Methods.OPTIONS), CONNECT( 34 | Methods.CONNECT), PATCH(new HttpString("PATCH")), LINK( 35 | new HttpString("LINK")), UNLINK(new HttpString("UNLINK")); 36 | 37 | static final Map methods = new HashMap<>(); 38 | static { 39 | for (HttpMethod hm : HttpMethod.values()) { 40 | methods.put(hm.rawdata, hm); 41 | } 42 | } 43 | 44 | HttpString rawdata; 45 | 46 | private HttpMethod(HttpString string) { 47 | this.rawdata = string; 48 | } 49 | 50 | @Override 51 | public boolean resolve(HttpServerExchange value) { 52 | return this.rawdata.equals(value.getRequestMethod()); 53 | } 54 | 55 | public static HttpMethod of(HttpServerExchange exchange) { 56 | return methods.getOrDefault(exchange.getRequestMethod(), GET); 57 | } 58 | 59 | public static Optional find(String method) { 60 | if (method == null || method.isEmpty()) { 61 | return Optional.empty(); 62 | } 63 | String m = method.toUpperCase(); 64 | return Optional.ofNullable(methods.get(new HttpString(m))).map( 65 | hm -> hm.rawdata); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/Request.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import io.undertow.server.HttpServerExchange; 19 | 20 | import java.io.File; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Optional; 24 | 25 | /** 26 | * @author taichi 27 | */ 28 | public interface Request extends AttributeContainer { 29 | 30 | HttpMethod method(); 31 | 32 | String path(); 33 | 34 | /** 35 | * get path parameter 36 | * 37 | * @param key 38 | * @return 39 | */ 40 | Optional params(String key); 41 | 42 | Map params(); 43 | 44 | /** 45 | * get query parameter 46 | * 47 | * @param key 48 | * @return 49 | */ 50 | Optional query(String key); 51 | 52 | Optional header(String name); 53 | 54 | List headers(String name); 55 | 56 | Map> headers(); 57 | 58 | Map cookies(); 59 | 60 | Optional cookie(String name); 61 | 62 | Optional form(String key); 63 | 64 | List forms(String key); 65 | 66 | Map> forms(); 67 | 68 | Optional file(String key); 69 | 70 | List files(String key); 71 | 72 | Map> files(); 73 | 74 | Optional body(); 75 | 76 | /** 77 | * get current session or create new session. 78 | * 79 | * @return session 80 | */ 81 | Session session(); 82 | 83 | /** 84 | * get current session 85 | * 86 | * @return session or empty 87 | */ 88 | Optional current(); 89 | 90 | boolean xhr(); 91 | 92 | String protocol(); 93 | 94 | String scheme(); 95 | 96 | HttpServerExchange raw(); 97 | 98 | } 99 | -------------------------------------------------------------------------------- /siden-example/src/main/java/example/UseHandlebars.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package example; 17 | 18 | import ninja.siden.App; 19 | import ninja.siden.Config; 20 | import ninja.siden.Renderer; 21 | import ninja.siden.RendererRepository; 22 | import ninja.siden.util.Suppress; 23 | 24 | import com.github.jknack.handlebars.Handlebars; 25 | import com.github.jknack.handlebars.Template; 26 | import com.github.jknack.handlebars.io.ClassPathTemplateLoader; 27 | import com.github.jknack.handlebars.io.TemplateLoader; 28 | 29 | /** 30 | * @author taichi 31 | */ 32 | public class UseHandlebars { 33 | 34 | public static void main(String[] args) throws Exception { 35 | App app = App.configure(conf -> { 36 | conf.set(Config.RENDERER_REPOSITORY, new HandleBarsRepo()); 37 | return conf; 38 | }); 39 | 40 | // see. https://github.com/jknack/handlebars.java 41 | Handlebars engine = new Handlebars(); 42 | Template t = engine.compileInline("Hello {{this}}!"); 43 | 44 | // use handlebars simply 45 | app.get("/bars", 46 | (req, res) -> res.render("john", Renderer.of(t::apply))); 47 | 48 | // read template from templates/say/hello.html 49 | app.get("/hello", 50 | (req, res) -> res.render(new User("peter"), "say/hello")); 51 | 52 | app.listen().addShutdownHook(); 53 | } 54 | 55 | static class HandleBarsRepo implements RendererRepository { 56 | final Handlebars engine; 57 | 58 | public HandleBarsRepo() { 59 | TemplateLoader loader = new ClassPathTemplateLoader(); 60 | loader.setPrefix("/templates"); 61 | loader.setSuffix(".html"); 62 | engine = new Handlebars(loader); 63 | } 64 | 65 | @Override 66 | public Renderer find(String path) { 67 | Template t = Suppress.get(() -> engine.compile(path)); 68 | return Renderer.of(t::apply); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/Core.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.server.HttpHandler; 19 | import io.undertow.server.HttpServerExchange; 20 | import io.undertow.util.AttachmentKey; 21 | 22 | import java.util.function.Predicate; 23 | 24 | import ninja.siden.Request; 25 | import ninja.siden.Response; 26 | 27 | import org.xnio.OptionMap; 28 | 29 | /** 30 | * @author taichi 31 | */ 32 | public class Core implements HttpHandler { 33 | 34 | public static final AttachmentKey CONFIG = AttachmentKey 35 | .create(OptionMap.class); 36 | 37 | public static final AttachmentKey REQUEST = AttachmentKey 38 | .create(Request.class); 39 | 40 | public static final AttachmentKey RESPONSE = AttachmentKey 41 | .create(Response.class); 42 | 43 | final OptionMap config; 44 | final HttpHandler next; 45 | 46 | public Core(OptionMap config, HttpHandler next) { 47 | super(); 48 | this.config = config; 49 | this.next = next; 50 | } 51 | 52 | @Override 53 | public void handleRequest(HttpServerExchange exchange) throws Exception { 54 | exchange.putAttachment(CONFIG, config); 55 | exchange.putAttachment(REQUEST, new SidenRequest(exchange)); 56 | exchange.putAttachment(RESPONSE, new SidenResponse(exchange)); 57 | exchange.addExchangeCompleteListener((ex, next) -> { 58 | try { 59 | exchange.removeAttachment(CONFIG); 60 | exchange.removeAttachment(REQUEST); 61 | exchange.removeAttachment(RESPONSE); 62 | } finally { 63 | next.proceed(); 64 | } 65 | }); 66 | next.handleRequest(exchange); 67 | } 68 | 69 | public static io.undertow.predicate.Predicate adapt(Predicate fn) { 70 | return exchange -> { 71 | Request request = exchange.getAttachment(Core.REQUEST); 72 | return fn.test(request); 73 | }; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/Connection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden; 17 | 18 | import io.undertow.websockets.core.WebSocketChannel; 19 | 20 | import java.io.OutputStream; 21 | import java.io.Writer; 22 | import java.nio.ByteBuffer; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Optional; 26 | import java.util.Set; 27 | import java.util.concurrent.CompletableFuture; 28 | 29 | import ninja.siden.util.ExceptionalConsumer; 30 | 31 | /** 32 | * @author taichi 33 | * @see io.undertow.websockets.core.WebSocketChannel 34 | */ 35 | public interface Connection extends AttributeContainer { 36 | 37 | // endpoint methods 38 | 39 | CompletableFuture send(String text); 40 | 41 | CompletableFuture send(ByteBuffer payload); 42 | 43 | CompletableFuture ping(ByteBuffer payload); 44 | 45 | CompletableFuture pong(ByteBuffer payload); 46 | 47 | CompletableFuture close(); 48 | 49 | CompletableFuture close(int code, String reason); 50 | 51 | void sendStream(ExceptionalConsumer fn); 52 | 53 | void sendWriter(ExceptionalConsumer fn); 54 | 55 | // informations 56 | 57 | String protocolVersion(); 58 | 59 | String subProtocol(); 60 | 61 | boolean secure(); 62 | 63 | boolean open(); 64 | 65 | Set peers(); 66 | 67 | // from request 68 | 69 | Optional params(String key); 70 | 71 | Map params(); 72 | 73 | Optional query(String key); 74 | 75 | Optional header(String name); 76 | 77 | List headers(String name); 78 | 79 | Map> headers(); 80 | 81 | Map cookies(); 82 | 83 | Optional cookie(String name); 84 | 85 | /** 86 | * get current session 87 | * 88 | * @return session or empty 89 | */ 90 | Optional current(); 91 | 92 | WebSocketChannel raw(); 93 | 94 | } 95 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/FiltersHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.server.HttpHandler; 19 | import io.undertow.server.HttpServerExchange; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import ninja.siden.FilterChain; 25 | import ninja.siden.Request; 26 | import ninja.siden.Response; 27 | import ninja.siden.def.FilterDef; 28 | 29 | /** 30 | * @author taichi 31 | */ 32 | public class FiltersHandler implements HttpHandler { 33 | 34 | HttpHandler next; 35 | 36 | List filters = new ArrayList<>(); 37 | 38 | public FiltersHandler(HttpHandler next) { 39 | this.next = next; 40 | } 41 | 42 | @Override 43 | public void handleRequest(HttpServerExchange exchange) throws Exception { 44 | if (filters.size() < 1) { 45 | next.handleRequest(exchange); 46 | return; 47 | } 48 | SimpleChain chain = new SimpleChain(exchange); 49 | chain.next(); 50 | } 51 | 52 | public void add(FilterDef model) { 53 | this.filters.add(model); 54 | } 55 | 56 | enum ChainState { 57 | HasNext, NoMore; 58 | } 59 | 60 | class SimpleChain implements FilterChain { 61 | 62 | int cursor; 63 | 64 | HttpServerExchange exchange; 65 | 66 | Request request; 67 | 68 | Response response; 69 | 70 | public SimpleChain(HttpServerExchange exchange) { 71 | super(); 72 | this.exchange = exchange; 73 | this.request = exchange.getAttachment(Core.REQUEST); 74 | this.response = exchange.getAttachment(Core.RESPONSE); 75 | } 76 | 77 | @Override 78 | public Object next() throws Exception { 79 | for (int index = cursor++; index < filters.size(); index = cursor++) { 80 | FilterDef f = filters.get(index); 81 | if (f.resolve(exchange)) { 82 | f.filter(request, response, this); 83 | return ChainState.HasNext; 84 | } 85 | } 86 | next.handleRequest(exchange); 87 | return ChainState.NoMore; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /siden-example/assets/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple WebSocket Chat 7 | 8 | 9 | 10 | 17 | 52 | 53 | 54 |
55 |
56 |
57 |
58 |
59 | 60 |
61 |
62 |
63 | 64 | 65 | 66 | 67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | 81 | 82 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/RequestMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | import io.undertow.server.handlers.MetricsHandler; 19 | import io.undertow.server.handlers.MetricsHandler.MetricResult; 20 | 21 | import java.beans.ConstructorProperties; 22 | import java.util.Date; 23 | 24 | /** 25 | * @author taichi 26 | */ 27 | public class RequestMetrics { 28 | 29 | Date metricsStartDate; 30 | 31 | long totalRequestTime; 32 | long maxRequestTime; 33 | long minRequestTime; 34 | long totalRequests; 35 | 36 | @ConstructorProperties({ "metricsStartDate", "totalRequestTime", 37 | "maxRequestTime", "minRequestTime", "totalRequests" }) 38 | public RequestMetrics(Date metricsStartDate, long totalRequestTime, 39 | long maxRequestTime, long minRequestTime, long totalRequests) { 40 | super(); 41 | this.metricsStartDate = metricsStartDate; 42 | this.totalRequestTime = totalRequestTime; 43 | this.maxRequestTime = maxRequestTime; 44 | this.minRequestTime = minRequestTime; 45 | this.totalRequests = totalRequests; 46 | } 47 | 48 | public static RequestMXBean to(MetricsHandler handler) { 49 | return new RequestMXBean() { 50 | 51 | @Override 52 | public void reset() { 53 | handler.reset(); 54 | } 55 | 56 | @Override 57 | public RequestMetrics getMetrics() { 58 | MetricResult result = handler.getMetrics(); 59 | return new RequestMetrics(result.getMetricsStartDate(), 60 | result.getTotalRequestTime(), 61 | result.getMaxRequestTime(), result.getMinRequestTime(), 62 | result.getTotalRequests()); 63 | } 64 | }; 65 | } 66 | 67 | public Date getMetricsStartDate() { 68 | return this.metricsStartDate; 69 | } 70 | 71 | public long getTotalRequestTime() { 72 | return this.totalRequestTime; 73 | } 74 | 75 | public long getMaxRequestTime() { 76 | return this.maxRequestTime; 77 | } 78 | 79 | public long getMinRequestTime() { 80 | return this.minRequestTime; 81 | } 82 | 83 | public long getTotalRequests() { 84 | return this.totalRequests; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/internal/MIMEPredicateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import io.undertow.predicate.Predicate; 20 | import io.undertow.server.HttpServerExchange; 21 | import io.undertow.util.Headers; 22 | 23 | import java.util.Arrays; 24 | 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.junit.runners.Parameterized; 29 | import org.junit.runners.Parameterized.Parameters; 30 | 31 | /** 32 | * @author taichi 33 | */ 34 | @RunWith(Parameterized.class) 35 | public class MIMEPredicateTest { 36 | 37 | HttpServerExchange exchange; 38 | 39 | @Before 40 | public void setUp() { 41 | this.exchange = new HttpServerExchange(null); 42 | } 43 | 44 | String wait; 45 | 46 | String request; 47 | 48 | boolean is; 49 | 50 | public MIMEPredicateTest(String wait, String request, boolean is) { 51 | this.wait = wait; 52 | this.request = request; 53 | this.is = is; 54 | } 55 | 56 | @Parameters(name = "{0} {1}") 57 | public static Iterable parameters() throws Exception { 58 | return Arrays.asList(new Object[][] { 59 | {"application/json", "application/json", true}, 60 | {"application/json", "APPLICATION/json", true}, 61 | {"application/json", "application/JSON", true}, 62 | 63 | {"application/*", "application/json", true}, 64 | {"*/json", "text/html", true}, 65 | {"*/*", "application/json", true}, 66 | {"*", "application/json", true}, 67 | 68 | {"application/json", "application/*", true}, 69 | {"text/html", "*/json",true}, 70 | {"application/json", "*/*",true}, 71 | {"application/json", "*", true}, 72 | 73 | {"application/json", "application/xml", false}, 74 | {"application/json", "text/json", false}, 75 | {"application/*", "text/json", false}, 76 | 77 | {"application/json", "text/*", false}, 78 | }); 79 | } 80 | 81 | @Test 82 | public void test() throws Exception { 83 | Predicate p = MIMEPredicate.accept(wait); 84 | this.exchange.getRequestHeaders().add(Headers.ACCEPT, request); 85 | assertEquals(is, p.resolve(exchange)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/SecurityHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.server.HttpHandler; 19 | import io.undertow.server.HttpServerExchange; 20 | import io.undertow.util.HeaderMap; 21 | import io.undertow.util.HeaderValues; 22 | import io.undertow.util.Headers; 23 | 24 | import java.util.Objects; 25 | 26 | import ninja.siden.Config; 27 | import ninja.siden.SecurityHeaders; 28 | 29 | import org.jboss.logging.Logger; 30 | import org.xnio.OptionMap; 31 | 32 | /** 33 | * @author taichi 34 | */ 35 | public class SecurityHandler implements HttpHandler { 36 | 37 | static final Logger LOG = Logger.getLogger(SecurityHandler.class); 38 | 39 | HttpHandler next; 40 | 41 | public SecurityHandler(HttpHandler next) { 42 | this.next = next; 43 | } 44 | 45 | @Override 46 | public void handleRequest(HttpServerExchange exchange) throws Exception { 47 | OptionMap config = exchange.getAttachment(Core.CONFIG); 48 | HeaderMap rh = exchange.getResponseHeaders(); 49 | 50 | rh.add(SecurityHeaders.FRAME_OPTIONS, config.get(Config.FRAME_OPTIONS)); 51 | 52 | if (config.get(Config.USE_XSS_PROTECTION)) { 53 | rh.add(SecurityHeaders.XSS_PROTECTION, "1; mode=block"); 54 | } 55 | 56 | if (config.get(Config.USE_CONTENT_TYPE_OPTIONS)) { 57 | rh.add(SecurityHeaders.CONTENT_TYPE_OPTIONS, "nosniff"); 58 | } 59 | 60 | exchange.addExchangeCompleteListener((ex, next) -> { 61 | try { 62 | if (rh.contains(Headers.CONTENT_TYPE) == false 63 | && rh.contains(Headers.SEC_WEB_SOCKET_ACCEPT) == false) { 64 | 65 | LOG.warn(ex.getRequestURI() 66 | + " Content-Type header doesn't exist."); 67 | } 68 | } finally { 69 | next.proceed(); 70 | } 71 | }); 72 | 73 | next.handleRequest(exchange); 74 | } 75 | 76 | static void addContentType(HttpServerExchange exchange) { 77 | SecurityHandler.addContentType(exchange, null); 78 | } 79 | 80 | static void addContentType(HttpServerExchange exchange, String type) { 81 | String t = Objects.toString(type, "application/octet-stream"); 82 | HeaderMap hm = exchange.getResponseHeaders(); 83 | HeaderValues hv = hm.get(Headers.CONTENT_TYPE); 84 | if (hv == null || hv.isEmpty()) { 85 | hm.add(Headers.CONTENT_TYPE, t); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /siden-example/src/main/java/example/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package example; 17 | 18 | import ninja.siden.App; 19 | import ninja.siden.Renderer; 20 | import ninja.siden.Request; 21 | 22 | import org.boon.json.JsonFactory; 23 | 24 | /** 25 | * @author taichi 26 | */ 27 | public class Main { 28 | 29 | public static void main(String[] args) throws Exception { 30 | App app = new App(); 31 | 32 | // simple get 33 | app.get("/hello", (req, res) -> "Hello world !!"); 34 | 35 | // receive Ajax request only 36 | app.get("/ajax", (req, res) -> "{ 'name' : 'ajax' }").match( 37 | Request::xhr); 38 | 39 | // simple logging filter 40 | app.use((req, res, chain) -> { 41 | System.out.printf("%s %s %n", req.method(), req.path()); 42 | chain.next(); 43 | }); 44 | 45 | // exception handling 46 | class MyException extends Exception { 47 | private static final long serialVersionUID = -2530468497731983302L; 48 | 49 | public String extramessage() { 50 | return "MyException has come!!"; 51 | } 52 | } 53 | app.error(MyException.class, (ex, req, res) -> { 54 | return ex.extramessage(); 55 | }); 56 | 57 | app.get("/err", (req, res) -> { 58 | throw new MyException(); 59 | }); 60 | 61 | // response code handling 62 | app.error(402, (req, res) -> "Payment Required. Payment Required!!"); 63 | app.get("/402", (req, res) -> 402); 64 | app.get("/payment", (req, res) -> res.status(402)); 65 | 66 | // json api on top of Boon JSON 67 | // see. https://github.com/boonproject/boon 68 | app.get("/users/:name", (req, res) -> req.params("name").map(User::new)) 69 | .render(Renderer.of(JsonFactory::toJson)) 70 | .type("application/json"); 71 | 72 | // use static resources 73 | // GET /javascripts/jquery.js 74 | // GET /style.css 75 | // GET /favicon.ico 76 | app.assets("/", "assets/"); 77 | 78 | // GET /static/javascripts/jquery.js 79 | // GET /static/style.css 80 | // GET /static/favicon.ico 81 | app.assets("/static", "assets/"); 82 | 83 | app.get("/", (req, res) -> "Siden Example Application is running."); 84 | 85 | // sub application 86 | App sub = new App(); 87 | // GET /secret/admin 88 | sub.get("/admin", (req, res) -> "I'm in secret area"); 89 | app.use("/secret", sub); 90 | 91 | app.listen().addShutdownHook(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/internal/FiltersHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import static org.junit.Assert.assertTrue; 19 | import io.undertow.predicate.Predicates; 20 | import io.undertow.server.HttpServerExchange; 21 | import mockit.Mock; 22 | import mockit.MockUp; 23 | import mockit.Mocked; 24 | import mockit.integration.junit4.JMockit; 25 | import ninja.siden.Filter; 26 | import ninja.siden.FilterChain; 27 | import ninja.siden.Request; 28 | import ninja.siden.Response; 29 | import ninja.siden.def.FilterDef; 30 | import ninja.siden.internal.FiltersHandler.SimpleChain; 31 | 32 | import org.junit.Before; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | 36 | /** 37 | * @author taichi 38 | */ 39 | @RunWith(JMockit.class) 40 | public class FiltersHandlerTest { 41 | 42 | HttpServerExchange exchange; 43 | 44 | @Mocked 45 | Request request; 46 | 47 | @Mocked 48 | Response response; 49 | 50 | @Before 51 | public void setUp() { 52 | this.exchange = new HttpServerExchange(null); 53 | this.exchange.putAttachment(Core.REQUEST, this.request); 54 | this.exchange.putAttachment(Core.RESPONSE, this.response); 55 | } 56 | 57 | @Test 58 | public void testNoFilter() throws Exception { 59 | new MockUp() { 60 | @Mock(invocations = 0) 61 | public Object next() throws Exception { 62 | return null; 63 | } 64 | }; 65 | boolean[] is = { false }; 66 | FiltersHandler target = new FiltersHandler(exc -> { 67 | is[0] = true; 68 | }); 69 | target.handleRequest(this.exchange); 70 | assertTrue(is[0]); 71 | } 72 | 73 | @Test 74 | public void testSimpleCall() throws Exception { 75 | Filter filter = new MockUp() { 76 | @Mock(invocations = 2) 77 | public void filter(Request req, Response res, FilterChain chain) 78 | throws Exception { 79 | chain.next(); 80 | } 81 | }.getMockInstance(); 82 | 83 | FiltersHandler target = new FiltersHandler(Testing.mustCall()); 84 | target.add(new FilterDef(Predicates.truePredicate(), filter)); 85 | target.add(new FilterDef(Predicates.falsePredicate(), 86 | (req, res, ch) -> { 87 | throw new AssertionError(); 88 | })); 89 | target.add(new FilterDef(Predicates.truePredicate(), filter)); 90 | 91 | target.handleRequest(this.exchange); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/internal/MethodOverrideHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import io.undertow.server.HttpHandler; 20 | import io.undertow.server.HttpServerExchange; 21 | import io.undertow.server.handlers.form.FormData; 22 | import io.undertow.server.handlers.form.FormDataParser; 23 | import io.undertow.util.AttachmentKey; 24 | import io.undertow.util.HeaderMap; 25 | import io.undertow.util.Methods; 26 | import mockit.Mock; 27 | import mockit.MockUp; 28 | import mockit.integration.junit4.JMockit; 29 | 30 | import org.junit.Before; 31 | import org.junit.Test; 32 | import org.junit.runner.RunWith; 33 | 34 | /** 35 | * @author taichi 36 | */ 37 | @RunWith(JMockit.class) 38 | public class MethodOverrideHandlerTest { 39 | 40 | HttpHandler target; 41 | 42 | @Before 43 | public void setUp() { 44 | this.target = new MethodOverrideHandler(Testing.mustCall()); 45 | } 46 | 47 | @Test 48 | public void testNotOverride() throws Exception { 49 | HttpServerExchange exchange = new MockUp() { 50 | @Mock 51 | public HeaderMap getRequestHeaders() { 52 | throw new AssertionError(); 53 | } 54 | 55 | @Mock 56 | public T getAttachment(final AttachmentKey key) { 57 | throw new AssertionError(); 58 | } 59 | }.getMockInstance(); 60 | 61 | exchange.setRequestMethod(Methods.GET); 62 | 63 | this.target.handleRequest(exchange); 64 | } 65 | 66 | @Test 67 | public void testOverrideFromHeader() throws Exception { 68 | HttpServerExchange exchange = new HttpServerExchange(null); 69 | exchange.setRequestMethod(Methods.POST); 70 | exchange.getRequestHeaders().put(MethodOverrideHandler.HEADER, 71 | "CONNECT"); 72 | this.target.handleRequest(exchange); 73 | assertEquals(Methods.CONNECT, exchange.getRequestMethod()); 74 | } 75 | 76 | @Test 77 | public void testOverrideFromForm() throws Exception { 78 | HttpServerExchange exchange = new HttpServerExchange(null); 79 | exchange.setRequestMethod(Methods.POST); 80 | 81 | FormData fd = new FormData(3); 82 | fd.add(MethodOverrideHandler.FORM, "PUT"); 83 | exchange.putAttachment(FormDataParser.FORM_DATA, fd); 84 | this.target.handleRequest(exchange); 85 | assertEquals(Methods.PUT, exchange.getRequestMethod()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/ReceiveListenerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.websockets.core.AbstractReceiveListener; 19 | import io.undertow.websockets.core.BufferedBinaryMessage; 20 | import io.undertow.websockets.core.BufferedTextMessage; 21 | import io.undertow.websockets.core.WebSocketChannel; 22 | 23 | import java.io.IOException; 24 | import java.nio.ByteBuffer; 25 | 26 | import ninja.siden.WebSocket; 27 | import ninja.siden.util.ExceptionalConsumer; 28 | 29 | import org.xnio.Pooled; 30 | 31 | /** 32 | * @author taichi 33 | */ 34 | public class ReceiveListenerAdapter extends AbstractReceiveListener { 35 | 36 | final WebSocket adaptee; 37 | 38 | public ReceiveListenerAdapter(WebSocket socket) { 39 | this.adaptee = socket; 40 | } 41 | 42 | @Override 43 | protected void onFullTextMessage(WebSocketChannel channel, 44 | BufferedTextMessage message) throws IOException { 45 | try { 46 | this.adaptee.onText(message.getData()); 47 | } catch (IOException e) { 48 | throw e; 49 | } catch (Exception e) { 50 | throw new IOException(e); 51 | } 52 | } 53 | 54 | @Override 55 | protected void onFullBinaryMessage(WebSocketChannel channel, 56 | BufferedBinaryMessage message) throws IOException { 57 | deliver(this.adaptee::onBinary, message); 58 | } 59 | 60 | void deliver(ExceptionalConsumer deliver, 61 | BufferedBinaryMessage message) throws IOException { 62 | Pooled pooled = message.getData(); 63 | try { 64 | deliver.accept(pooled.getResource()); 65 | } catch (IOException e) { 66 | throw e; 67 | } catch (Exception e) { 68 | throw new IOException(e); 69 | } finally { 70 | pooled.free(); 71 | } 72 | } 73 | 74 | @Override 75 | protected void onFullPongMessage(WebSocketChannel channel, 76 | BufferedBinaryMessage message) throws IOException { 77 | deliver(this.adaptee::onPong, message); 78 | } 79 | 80 | @Override 81 | protected void onFullPingMessage(WebSocketChannel channel, 82 | BufferedBinaryMessage message) throws IOException { 83 | deliver(this.adaptee::onPing, message); 84 | } 85 | 86 | @Override 87 | protected void onFullCloseMessage(WebSocketChannel channel, 88 | BufferedBinaryMessage message) throws IOException { 89 | deliver(this.adaptee::onClose, message); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/RequestMeter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | import java.util.Date; 19 | import java.util.concurrent.atomic.AtomicLongFieldUpdater; 20 | import java.util.concurrent.atomic.LongAccumulator; 21 | import java.util.concurrent.atomic.LongAdder; 22 | 23 | import ninja.siden.util.ExceptionalConsumer; 24 | import ninja.siden.util.ExceptionalFunction; 25 | import ninja.siden.util.LongAccumulators; 26 | 27 | /** 28 | * @author taichi 29 | */ 30 | public class RequestMeter { 31 | 32 | static final AtomicLongFieldUpdater startTimeUpdater = AtomicLongFieldUpdater 33 | .newUpdater(RequestMeter.class, "startTime"); 34 | 35 | volatile long startTime; 36 | LongAdder totalRequestTime; 37 | LongAccumulator maxRequestTime; 38 | LongAccumulator minRequestTime; 39 | LongAdder totalRequests; 40 | 41 | public RequestMeter() { 42 | this.startTime = System.currentTimeMillis(); 43 | this.totalRequestTime = new LongAdder(); 44 | this.maxRequestTime = LongAccumulators.max(); 45 | this.minRequestTime = LongAccumulators.min(); 46 | this.totalRequests = new LongAdder(); 47 | } 48 | 49 | protected void accept(final long requestTime) { 50 | this.totalRequestTime.add(requestTime); 51 | this.maxRequestTime.accumulate(requestTime); 52 | this.minRequestTime.accumulate(requestTime); 53 | this.totalRequests.increment(); 54 | } 55 | 56 | public void accept( 57 | ExceptionalConsumer fn) throws EX { 58 | apply(m -> { 59 | fn.accept(m); 60 | return null; 61 | }); 62 | } 63 | 64 | public R apply( 65 | ExceptionalFunction fn) throws EX { 66 | final long start = System.currentTimeMillis(); 67 | try { 68 | return fn.apply(this); 69 | } finally { 70 | accept(System.currentTimeMillis() - start); 71 | } 72 | } 73 | 74 | public void reset() { 75 | startTimeUpdater.set(this, System.currentTimeMillis()); 76 | this.totalRequestTime.reset(); 77 | this.maxRequestTime.reset(); 78 | this.minRequestTime.reset(); 79 | this.totalRequests.reset(); 80 | } 81 | 82 | public RequestMetrics toMetrics() { 83 | return new RequestMetrics(new Date(this.startTime), 84 | this.totalRequestTime.sum(), this.maxRequestTime.get(), 85 | this.minRequestTime.get(), this.totalRequests.sum()); 86 | } 87 | } -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/internal/Testing.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.server.HttpHandler; 19 | import io.undertow.server.HttpServerExchange; 20 | 21 | import java.nio.charset.StandardCharsets; 22 | import java.util.Scanner; 23 | import java.util.logging.ConsoleHandler; 24 | import java.util.logging.Level; 25 | import java.util.logging.Logger; 26 | 27 | import mockit.Mock; 28 | import mockit.MockUp; 29 | import ninja.siden.util.Using; 30 | 31 | import org.apache.http.HttpEntity; 32 | import org.apache.http.HttpResponse; 33 | import org.apache.http.client.methods.HttpUriRequest; 34 | import org.apache.http.config.SocketConfig; 35 | import org.apache.http.impl.client.CloseableHttpClient; 36 | import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; 37 | import org.apache.http.impl.client.HttpClientBuilder; 38 | 39 | /** 40 | * @author taichi 41 | */ 42 | public interface Testing { 43 | 44 | static CloseableHttpClient client() { 45 | HttpClientBuilder builder = HttpClientBuilder.create(); 46 | builder.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(2000) 47 | .build()); 48 | builder.setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)); 49 | return builder.build(); 50 | } 51 | 52 | @FunctionalInterface 53 | interface ResponseConsumer { 54 | void accept(HttpResponse response) throws Exception; 55 | } 56 | 57 | static void request(HttpUriRequest request, ResponseConsumer fn) 58 | throws Exception { 59 | Using.consume(Testing::client, c -> fn.accept(c.execute(request))); 60 | } 61 | 62 | static String read(HttpResponse response) throws Exception { 63 | HttpEntity entity = response.getEntity(); 64 | if (entity == null) { 65 | return ""; 66 | } 67 | try (Scanner scanner = new Scanner(entity.getContent(), 68 | StandardCharsets.UTF_8.name())) { 69 | return scanner.useDelimiter("\\A").next(); 70 | } 71 | } 72 | 73 | static HttpHandler mustCall() { 74 | return new MockUp() { 75 | @Mock(invocations = 1) 76 | public void handleRequest(HttpServerExchange exchange) 77 | throws Exception { 78 | } 79 | }.getMockInstance(); 80 | } 81 | 82 | static HttpHandler empty() { 83 | return exc -> { 84 | throw new AssertionError(); 85 | }; 86 | } 87 | 88 | static void useALL(Class target) { 89 | ConsoleHandler h = new ConsoleHandler(); 90 | h.setLevel(Level.ALL); 91 | Logger logger = Logger.getLogger(target.getName()); 92 | logger.addHandler(h); 93 | logger.setLevel(Level.ALL); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /siden-react/README.md: -------------------------------------------------------------------------------- 1 | # React.js Server Side Rendering Support 2 | 3 | ## Installation 4 | 5 | ### get console-polyfill 6 | 7 | * [console-polyfill](https://github.com/paulmillr/console-polyfill) 8 | 9 | Because Nashorn don't contain `console` object. 10 | 11 | ### Setup your javascript build environment 12 | 13 | like below 14 | ``` 15 | npm install -g react-tools 16 | jsx --watch assets/src build 17 | ``` 18 | 19 | siden-react don't build jsx. 20 | 21 | ### Pick your favorite JSON Serializer and Template Engine 22 | 23 | you may use any JSON Serializer such as 24 | 25 | * [Jackson](http://jackson.codehaus.org/) 26 | * [google-gson](https://code.google.com/p/google-gson/) 27 | * [boon](https://github.com/boonproject/boon) 28 | 29 | i recommend you to use [boon](https://github.com/boonproject/boon). 30 | 31 | you may use any template engine such as 32 | 33 | * [Thymeleaf](http://www.thymeleaf.org/) 34 | * [handlebars.java](https://github.com/jknack/handlebars.java) 35 | * [mustache.java](https://github.com/spullara/mustache.java) 36 | 37 | i recommend you to use [mustache.java](https://github.com/spullara/mustache.java). 38 | 39 | ### Add dependency to your build.gradle 40 | 41 | ```groovy 42 | apply plugin: 'java' 43 | 44 | repositories.jcenter() 45 | 46 | dependencies { 47 | compile 'ninja.siden:siden-react:0.2.0' 48 | } 49 | 50 | sourceCompatibility = targetCompatibility = 1.8 51 | ``` 52 | 53 | ### Example 54 | 55 | ```java 56 | package example; 57 | 58 | import java.nio.file.Paths; 59 | import java.util.Arrays; 60 | 61 | import ninja.siden.App; 62 | import ninja.siden.react.React; 63 | 64 | public class UseReactSSR { 65 | 66 | public static void main(String[] args) { 67 | // setup react server side rendering 68 | React rc = new React("HelloMessage", "content", Arrays.asList( 69 | // https://github.com/paulmillr/console-polyfill 70 | // Nashorn don't contain console object. 71 | Paths.get("assets", "console-polyfill.js"), 72 | // https://github.com/facebook/react 73 | Paths.get("assets", "react.js"), 74 | // npm install -g react-tools 75 | // jsx -x jsx assets build 76 | // siden-react don't support jsx compile. 77 | Paths.get("build", "hello.js"))); 78 | 79 | App app = new App(); 80 | app.get("/", (q, s) -> { 81 | // serialized json 82 | String props = "{\"name\":\"john\"}"; 83 | // server side rendering 84 | return "" + rc.toHtml(props) + ""; 85 | }).type("text/html"); 86 | app.listen(); 87 | } 88 | } 89 | ``` 90 | 91 | ```javascript 92 | /** @jsx React.DOM */ 93 | var HelloMessage = React.createClass({ 94 | render: function() { 95 | return
Hello {this.props.name}
; 96 | } 97 | }); 98 | ``` 99 | 100 | if you want to more complex example, see [here](https://github.com/taichi/siden/tree/master/siden-example/src/main/java/example/UseReactComplexSSR.java) 101 | 102 | ## Similar projects 103 | * [React.NET](https://github.com/reactjs/React.NET) 104 | * [Om Server Rendering](https://github.com/pleasetrythisathome/om-server-rendering) 105 | * [jreact](https://github.com/KnisterPeter/jreact/) 106 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/MIMEPredicate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.predicate.Predicate; 19 | import io.undertow.server.HttpServerExchange; 20 | import io.undertow.util.Headers; 21 | import io.undertow.util.HttpString; 22 | import io.undertow.util.QValueParser; 23 | 24 | import java.util.List; 25 | import java.util.regex.Matcher; 26 | import java.util.regex.Pattern; 27 | 28 | /** 29 | * @author taichi 30 | */ 31 | public class MIMEPredicate implements Predicate { 32 | 33 | static final Pattern MIME = Pattern 34 | .compile("(?[*\\w]+)/(?[*-.\\w]+)(;(.*))?"); 35 | 36 | final HttpString name; 37 | 38 | final String type; 39 | 40 | final String subType; 41 | 42 | public static Predicate accept(String type) { 43 | return new MIMEPredicate(Headers.ACCEPT, type); 44 | } 45 | 46 | public static Predicate contentType(String type) { 47 | return new MIMEPredicate(Headers.CONTENT_TYPE, type); 48 | } 49 | 50 | public MIMEPredicate(HttpString name, String contentType) { 51 | super(); 52 | this.name = name; 53 | if (wildCard(contentType)) { 54 | this.type = this.subType = "*"; 55 | } else { 56 | Matcher m = MIME.matcher(contentType); 57 | if (m.find()) { 58 | this.type = m.group("type"); 59 | this.subType = m.group("subtype"); 60 | } else { 61 | throw new IllegalArgumentException("contentType"); 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | public boolean resolve(HttpServerExchange value) { 68 | final List res = value.getRequestHeaders().get(name); 69 | if (res == null || res.isEmpty()) { 70 | return false; 71 | } 72 | final List> found = QValueParser 73 | .parse(res); 74 | return found.stream().flatMap(List::stream).anyMatch(this::match); 75 | } 76 | 77 | boolean wildCard(String s) { 78 | return s.equals("*"); 79 | } 80 | 81 | boolean match(QValueParser.QValueResult result) { 82 | String v = result.getValue(); 83 | if (wildCard(v) || wildCard(this.type)) { 84 | return true; 85 | } 86 | Matcher m = MIME.matcher(v); 87 | if (m.find() == false) { 88 | return false; 89 | } 90 | String t = m.group("type"); 91 | if (wildCard(t)) { 92 | return true; 93 | } 94 | if (this.type.equalsIgnoreCase(t)) { 95 | String sub = m.group("subtype"); 96 | if (wildCard(sub) || wildCard(this.subType) 97 | || this.subType.equalsIgnoreCase(sub)) { 98 | return true; 99 | } 100 | } 101 | return false; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/PathPredicate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.predicate.Predicate; 19 | import io.undertow.server.HttpServerExchange; 20 | import io.undertow.util.AttachmentKey; 21 | 22 | import java.util.ArrayList; 23 | import java.util.HashMap; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.Objects; 27 | import java.util.regex.Matcher; 28 | import java.util.regex.Pattern; 29 | 30 | import org.jboss.logging.Logger; 31 | 32 | /** 33 | * @author taichi 34 | */ 35 | public class PathPredicate implements Predicate { 36 | 37 | static final Logger LOG = Logger.getLogger(PathPredicate.class); 38 | 39 | public static final AttachmentKey> PARAMS = AttachmentKey 40 | .create(Map.class); 41 | 42 | static final Pattern NAMED = Pattern 43 | .compile("\\(\\?\\<(?\\w+)\\>[^)]+\\)"); 44 | static final Pattern SEGMENT = Pattern 45 | .compile("(?[/\\.])?(?::(?\\w+))"); 46 | 47 | Pattern template; 48 | List names = new ArrayList<>(); 49 | 50 | public PathPredicate(Pattern template) { 51 | this.template = template; 52 | for (Matcher m = NAMED.matcher(template.pattern()); m.find();) { 53 | names.add(m.group("name")); 54 | } 55 | LOG.debug(names); 56 | } 57 | 58 | public PathPredicate(String template) { 59 | StringBuilder stb = new StringBuilder(template); 60 | Matcher m = SEGMENT.matcher(stb); 61 | int index = 0; 62 | while (index < stb.length() && m.find(index)) { 63 | names.add(m.group("name")); 64 | LOG.debug(names); 65 | String v = makeReplacement(m.group("prefix")); 66 | index = m.start() + v.length(); 67 | stb.replace(m.start(), m.end(), v); 68 | m = SEGMENT.matcher(stb); 69 | } 70 | stb.append("(?:.*)"); 71 | LOG.debug(stb); 72 | this.template = Pattern.compile(stb.toString()); 73 | } 74 | 75 | String makeReplacement(String prefix) { 76 | String pref = Objects.toString(prefix, ""); 77 | if (".".equals(pref)) { 78 | pref = "\\."; 79 | } 80 | return pref + "([^\\/]+)"; 81 | } 82 | 83 | @Override 84 | public boolean resolve(HttpServerExchange exchange) { 85 | Matcher m = this.template.matcher(exchange.getRelativePath()); 86 | if (m.matches()) { 87 | int count = m.groupCount(); 88 | if (count <= names.size()) { 89 | Map newone = new HashMap<>(); 90 | for (int i = 0; i < count; i++) { 91 | newone.put(names.get(i), m.group(i + 1)); 92 | } 93 | exchange.putAttachment(PARAMS, newone); 94 | } 95 | return true; 96 | } 97 | return false; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /siden-example/assets/comments.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var converter = new Showdown.converter(); 3 | var Comment = React.createClass({ 4 | render: function() { 5 | var rawMarkup = converter.makeHtml(this.props.children.toString()); 6 | return ( 7 |
8 |

9 | {this.props.author} 10 |

11 | 12 |
13 | ); 14 | } 15 | }); 16 | var CommentList = React.createClass({ 17 | render: function() { 18 | var commentNodes = this.props.data.map(function (comment) { 19 | return ( 20 | // http://facebook.github.io/react/docs/multiple-components.html#dynamic-children 21 | 22 | {comment.text} 23 | 24 | ); 25 | }); 26 | return ( 27 |
28 | {commentNodes} 29 |
30 | ); 31 | } 32 | }); 33 | var CommentForm = React.createClass({ 34 | handleSubmit: function(e) { 35 | e.preventDefault(); 36 | var author = this.refs.author.getDOMNode().value.trim(); 37 | var text = this.refs.text.getDOMNode().value.trim(); 38 | if (!text || !author) { 39 | return; 40 | } 41 | this.props.onCommentSubmit({author: author, text: text}); 42 | this.refs.author.getDOMNode().value = ''; 43 | this.refs.text.getDOMNode().value = ''; 44 | return; 45 | }, 46 | render: function() { 47 | return ( 48 |
49 | 50 | 51 | 52 |
53 | ); 54 | } 55 | }); 56 | var CommentBox = React.createClass({ 57 | getInitialState: function() { 58 | return { data: this.props.initdata }; 59 | }, 60 | loadCommentsFromServer: function() { 61 | $.ajax({ 62 | url: this.props.url, 63 | dataType: 'json', 64 | success: function(data) { 65 | this.setState({data: data}); 66 | }.bind(this), 67 | error: function(xhr, status, err) { 68 | console.error(this.props.url, status, err.toString()); 69 | }.bind(this) 70 | }); 71 | }, 72 | handleCommentSubmit: function(comment) { 73 | // var comments = this.state.data; 74 | // var newComments = comments.concat([comment]); 75 | // this.setState({data: newComments}); 76 | $.ajax({ 77 | url: this.props.url, 78 | dataType: 'json', 79 | type: 'POST', 80 | data: comment, 81 | success: function(data) { 82 | this.setState({data: data}); 83 | }.bind(this), 84 | error: function(xhr, status, err) { 85 | console.error(this.props.url, status, err.toString()); 86 | }.bind(this) 87 | }); 88 | }, 89 | componentDidMount: function() { 90 | this.loadCommentsFromServer(); 91 | //setInterval(this.loadCommentsFromServer, this.props.pollInterval); 92 | }, 93 | render: function() { 94 | return ( 95 |
96 |

Comments

97 | 98 | 99 |
100 | ); 101 | } 102 | }); 103 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/internal/RendererSelectorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import java.io.ByteArrayInputStream; 21 | import java.nio.ByteBuffer; 22 | import java.nio.channels.FileChannel; 23 | import java.nio.file.Files; 24 | import java.nio.file.Path; 25 | import java.util.Arrays; 26 | 27 | import ninja.siden.App; 28 | import ninja.siden.Stoppable; 29 | 30 | import org.apache.http.client.methods.HttpGet; 31 | import org.junit.After; 32 | import org.junit.Before; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | import org.junit.runners.Parameterized; 36 | import org.junit.runners.Parameterized.Parameters; 37 | 38 | /** 39 | * @author taichi 40 | */ 41 | @RunWith(Parameterized.class) 42 | public class RendererSelectorTest { 43 | 44 | App app; 45 | 46 | Stoppable stopper; 47 | 48 | @Before 49 | public void setUp() { 50 | this.app = new App(); 51 | } 52 | 53 | @After 54 | public void tearDown() { 55 | this.stopper.stop(); 56 | } 57 | 58 | Object actual; 59 | int port; 60 | 61 | public RendererSelectorTest(String name, Object data, int port) { 62 | this.actual = data; 63 | this.port = port; 64 | } 65 | 66 | @Parameters(name = "{0}") 67 | public static Iterable parameters() throws Exception { 68 | int port = 8000; 69 | return Arrays.asList(new Object[][] { 70 | { "String", "Hello", port++ }, 71 | { "File", tmp().toFile(), port++ }, 72 | { "Path", tmp(), port++ }, 73 | { "FileChannel", FileChannel.open(tmp()), port++ }, 74 | { "byteArray", "Hello".getBytes(), port++ }, 75 | { "ByteBuffer", ByteBuffer.wrap("Hello".getBytes()), port++ }, 76 | { "URI", tmp().toUri(), port++ }, 77 | { "URL", tmp().toUri().toURL(), port++ }, 78 | { "Reader", Files.newBufferedReader(tmp()), port++ }, 79 | { "InputStream", new ByteArrayInputStream("Hello".getBytes()), 80 | port++ }, 81 | { "CharSequence", new StringBuilder("Hello"), port++ }, 82 | 83 | }); 84 | } 85 | 86 | void runServer(Object result) { 87 | this.app.get("/renderer", (req, res) -> result); 88 | this.stopper = this.app.listen(port); 89 | } 90 | 91 | HttpGet make() { 92 | return new HttpGet("http://localhost:" + port + "/renderer"); 93 | } 94 | 95 | void request() throws Exception { 96 | Testing.request(make(), response -> { 97 | assertEquals(200, response.getStatusLine().getStatusCode()); 98 | assertEquals("Hello", Testing.read(response)); 99 | }); 100 | } 101 | 102 | @Test 103 | public void test() throws Exception { 104 | runServer(this.actual); 105 | request(); 106 | } 107 | 108 | static Path tmp() throws Exception { 109 | Path path = Files.createTempFile(RendererSelectorTest.class.getName(), 110 | ".tmp"); 111 | Files.write(path, "Hello".getBytes()); 112 | return path; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/WebSocketTracker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | import java.nio.ByteBuffer; 19 | 20 | import ninja.siden.Connection; 21 | import ninja.siden.WebSocket; 22 | import ninja.siden.WebSocketFactory; 23 | 24 | /** 25 | * @author taichi 26 | */ 27 | public class WebSocketTracker implements WebSocketFactory, WebSocketMXBean { 28 | 29 | final WebSocketFactory original; 30 | 31 | RequestMeter onConnect = new RequestMeter(); 32 | RequestMeter onText = new RequestMeter(); 33 | RequestMeter onBinary = new RequestMeter(); 34 | RequestMeter onPong = new RequestMeter(); 35 | RequestMeter onPing = new RequestMeter(); 36 | RequestMeter onClose = new RequestMeter(); 37 | 38 | public WebSocketTracker(WebSocketFactory original) { 39 | this.original = original; 40 | } 41 | 42 | @Override 43 | public WebSocket create(Connection connection) { 44 | return new WsWrapper(this.original.create(connection)); 45 | } 46 | 47 | class WsWrapper implements WebSocket { 48 | final WebSocket original; 49 | 50 | public WsWrapper(WebSocket original) { 51 | this.original = original; 52 | } 53 | 54 | @Override 55 | public void onConnect(Connection connection) throws Exception { 56 | onConnect.accept(m -> original.onConnect(connection)); 57 | } 58 | 59 | @Override 60 | public void onText(String payload) throws Exception { 61 | onText.accept(m -> original.onText(payload)); 62 | } 63 | 64 | @Override 65 | public void onBinary(ByteBuffer[] payload) throws Exception { 66 | onBinary.accept(m -> original.onBinary(payload)); 67 | } 68 | 69 | @Override 70 | public void onPong(ByteBuffer[] payload) throws Exception { 71 | onPong.accept(m -> original.onPong(payload)); 72 | } 73 | 74 | @Override 75 | public void onPing(ByteBuffer[] payload) throws Exception { 76 | onPing.accept(m -> original.onPing(payload)); 77 | } 78 | 79 | @Override 80 | public void onClose(ByteBuffer[] payload) throws Exception { 81 | onClose.accept(m -> original.onClose(payload)); 82 | } 83 | } 84 | 85 | @Override 86 | public void reset() { 87 | this.onConnect.reset(); 88 | this.onText.reset(); 89 | this.onBinary.reset(); 90 | this.onPong.reset(); 91 | this.onPing.reset(); 92 | this.onClose.reset(); 93 | } 94 | 95 | @Override 96 | public RequestMetrics getOnConnect() { 97 | return onConnect.toMetrics(); 98 | } 99 | 100 | @Override 101 | public RequestMetrics getOnText() { 102 | return onText.toMetrics(); 103 | } 104 | 105 | @Override 106 | public RequestMetrics getOnBinary() { 107 | return onBinary.toMetrics(); 108 | } 109 | 110 | @Override 111 | public RequestMetrics getOnPong() { 112 | return onPong.toMetrics(); 113 | } 114 | 115 | @Override 116 | public RequestMetrics getOnPing() { 117 | return onPing.toMetrics(); 118 | } 119 | 120 | @Override 121 | public RequestMetrics getOnClose() { 122 | return onClose.toMetrics(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /siden-example/src/main/java/example/UseReactComplexSSR.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package example; 17 | 18 | import java.nio.file.Paths; 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.regex.Pattern; 25 | 26 | import ninja.siden.App; 27 | import ninja.siden.Renderer; 28 | import ninja.siden.RoutingCustomizer; 29 | import ninja.siden.react.React; 30 | 31 | import org.boon.Maps; 32 | import org.boon.json.JsonFactory; 33 | 34 | import com.github.mustachejava.DefaultMustacheFactory; 35 | import com.github.mustachejava.Mustache; 36 | import com.github.mustachejava.MustacheFactory; 37 | 38 | /** 39 | * React.js Server Side Rendering Example on JVM. 40 | * 41 | * @author taichi 42 | */ 43 | public class UseReactComplexSSR { 44 | static int id = 0; 45 | 46 | public static void main(String[] args) { 47 | // test data 48 | List> comments = new ArrayList<>(); 49 | comments.add(Maps.map("id", ++id, "author", 50 | "Pete Hunt", "text", "This is one comment")); 51 | comments.add(Maps.map("id", ++id, "author", 52 | "Jordan Walke", "text", "This is *another* comment")); 53 | 54 | // setup template engine 55 | MustacheFactory mf = new DefaultMustacheFactory(); 56 | Mustache template = mf.compile("assets/react.mustache"); 57 | Renderer renderer = Renderer.of((m, w) -> template.execute(w, m)); 58 | 59 | // setup react server side rendering 60 | React rc = new React("CommentBox", "content", Arrays.asList( 61 | // https://github.com/paulmillr/console-polyfill 62 | // Nashorn doesn't contain console object. 63 | Paths.get("assets", "console-polyfill.js"), 64 | // http://facebook.github.io/react/ 65 | Paths.get("assets", "react.js"), 66 | // https://github.com/showdownjs/showdown 67 | Paths.get("assets", "showdown.min.js"), 68 | // npm install -g react-tools 69 | // jsx --watch assets\src build 70 | Paths.get("build", "comments.js"))); 71 | 72 | App app = new App(); 73 | app.get(Pattern.compile("/(index.html?)?"), 74 | (q, s) -> { 75 | String props = JsonFactory.toJson(Maps.map("initdata", 76 | comments, "url", "comments.json")); 77 | Map model = new HashMap<>(); 78 | model.put("rendered", rc.toHtml(props)); 79 | model.put("clientjs", rc.toClientJs(props)); 80 | return s.render(model, renderer); 81 | }).type("text/html"); 82 | 83 | // JSON API 84 | json(app.get("/comments.json", (q, s) -> comments)); 85 | json(app.post("/comments.json", (req, res) -> { 86 | Map m = new HashMap<>(); 87 | m.put("id", ++id); 88 | req.form("author").ifPresent(s -> m.put("author", s)); 89 | req.form("text").ifPresent(s -> m.put("text", s)); 90 | comments.add(m); 91 | return comments; 92 | })); 93 | 94 | app.assets("build"); 95 | app.assets("/static", "assets"); 96 | app.listen().addShutdownHook(); 97 | } 98 | 99 | static void json(RoutingCustomizer route) { 100 | route.render(Renderer.of(JsonFactory::toJson)).type("application/json"); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/internal/SidenRequestTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertFalse; 20 | import static org.junit.Assert.assertTrue; 21 | 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.Optional; 24 | import java.util.concurrent.ArrayBlockingQueue; 25 | import java.util.concurrent.BlockingQueue; 26 | import java.util.concurrent.CountDownLatch; 27 | 28 | import ninja.siden.App; 29 | import ninja.siden.Stoppable; 30 | 31 | import org.apache.http.client.methods.HttpPost; 32 | import org.apache.http.entity.StringEntity; 33 | import org.junit.After; 34 | import org.junit.Before; 35 | import org.junit.Test; 36 | 37 | /** 38 | * @author taichi 39 | */ 40 | public class SidenRequestTest { 41 | 42 | static int port = 7000; 43 | 44 | App app; 45 | 46 | Stoppable stopper; 47 | 48 | @Before 49 | public void setUp() { 50 | this.app = new App(); 51 | 52 | } 53 | 54 | @After 55 | public void tearDown() { 56 | this.stopper.stop(); 57 | port++; 58 | } 59 | 60 | @Test 61 | public void largeStringBody() throws Exception { 62 | StringBuilder stb = new StringBuilder(); 63 | for (int i = 0; i < 1500; i++) { 64 | stb.append("0123456789"); 65 | } 66 | stringBody(stb.toString()); 67 | } 68 | 69 | @Test 70 | public void stringBodyWithLF() throws Exception { 71 | StringBuilder stb = new StringBuilder(); 72 | for (int i = 0; i < 100; i++) { 73 | stb.append("abcd\r\n"); 74 | } 75 | String s = stb.toString(); 76 | Optional opt = stringBody(s); 77 | assertEquals(s, opt.get()); 78 | } 79 | 80 | @Test 81 | public void stringBodySwithJapanese() throws Exception { 82 | String s = "abcd~~wayway美豚"; 83 | Optional opt = stringBody(s); 84 | assertEquals(s, opt.get()); 85 | } 86 | 87 | @Test 88 | public void smallStringBody() throws Exception { 89 | assertTrue(stringBody("hoge").isPresent()); 90 | } 91 | 92 | @Test 93 | public void noBody() throws Exception { 94 | assertFalse(stringBody("").isPresent()); 95 | } 96 | 97 | public Optional stringBody(String body) throws Exception { 98 | CountDownLatch latch = new CountDownLatch(2); 99 | BlockingQueue> queue = new ArrayBlockingQueue<>(1); 100 | this.app.post("/string", (req, res) -> { 101 | Optional content = req.body(); 102 | queue.add(content); 103 | latch.countDown(); 104 | return content; 105 | }); 106 | this.stopper = this.app.listen(port); 107 | 108 | HttpPost post = new HttpPost(String.format( 109 | "http://localhost:%d/string", port)); 110 | post.setHeader("Content-Type", "application/string;charset=UTF-8"); 111 | post.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); 112 | 113 | Testing.request(post, response -> { 114 | try { 115 | assertEquals(200, response.getStatusLine().getStatusCode()); 116 | if (body.isEmpty() == false) { 117 | assertEquals(body, Testing.read(response)); 118 | } 119 | } finally { 120 | latch.countDown(); 121 | } 122 | }); 123 | latch.await(); 124 | return queue.poll(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/SessionMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | import io.undertow.server.session.SessionManagerStatistics; 19 | 20 | import java.beans.ConstructorProperties; 21 | 22 | /** 23 | * @author taichi 24 | */ 25 | public class SessionMetrics { 26 | 27 | final long startTime; 28 | final long createdSessionCount; 29 | final long maxActiveSessions; 30 | final long activeSessionCount; 31 | final long expiredSessionCount; 32 | final long rejectedSessionCount; 33 | final long maxSessionAliveTime; 34 | final long averageSessionAliveTime; 35 | 36 | @ConstructorProperties({ "startTime", "createdSessionCount", 37 | "maxActiveSessions", "activeSessionCount", "expiredSessionCount", 38 | "rejectedSessionCount", "maxSessionAliveTime", 39 | "averageSessionAliveTime" }) 40 | public SessionMetrics(long startTime, long createdSessionCount, 41 | long maxActiveSessions, long activeSessionCount, 42 | long expiredSessionCount, long rejectedSessionCount, 43 | long maxSessionAliveTime, long averageSessionAliveTime) { 44 | super(); 45 | this.startTime = startTime; 46 | this.createdSessionCount = createdSessionCount; 47 | this.maxActiveSessions = maxActiveSessions; 48 | this.activeSessionCount = activeSessionCount; 49 | this.expiredSessionCount = expiredSessionCount; 50 | this.rejectedSessionCount = rejectedSessionCount; 51 | this.maxSessionAliveTime = maxSessionAliveTime; 52 | this.averageSessionAliveTime = averageSessionAliveTime; 53 | } 54 | 55 | public static SessionMXBean to(SessionManagerStatistics stats) { 56 | return () -> new SessionMetrics(stats.getStartTime(), 57 | stats.getCreatedSessionCount(), stats.getMaxActiveSessions(), 58 | stats.getActiveSessionCount(), stats.getExpiredSessionCount(), 59 | stats.getRejectedSessions(), stats.getMaxSessionAliveTime(), 60 | stats.getAverageSessionAliveTime()); 61 | } 62 | 63 | /** 64 | * 65 | * @return The number of sessions that this session manager has created 66 | */ 67 | public long getCreatedSessionCount() { 68 | return this.createdSessionCount; 69 | } 70 | 71 | /** 72 | * 73 | * @return the maximum number of sessions this session manager supports 74 | */ 75 | public long getMaxActiveSessions() { 76 | return this.maxActiveSessions; 77 | } 78 | 79 | /** 80 | * 81 | * @return The number of active sessions 82 | */ 83 | public long getActiveSessionCount() { 84 | return this.activeSessionCount; 85 | } 86 | 87 | /** 88 | * 89 | * @return The number of expired sessions 90 | */ 91 | public long getExpiredSessionCount() { 92 | return this.expiredSessionCount; 93 | } 94 | 95 | /** 96 | * 97 | * @return The number of rejected sessions 98 | */ 99 | public long getRejectedSessions() { 100 | return this.rejectedSessionCount; 101 | } 102 | 103 | /** 104 | * 105 | * @return The longest a session has been alive for in milliseconds 106 | */ 107 | public long getMaxSessionAliveTime() { 108 | return this.maxSessionAliveTime; 109 | } 110 | 111 | /** 112 | * 113 | * @return The average session lifetime in milliseconds 114 | */ 115 | public long getAverageSessionAliveTime() { 116 | return this.averageSessionAliveTime; 117 | } 118 | 119 | /** 120 | * 121 | * @return The timestamp at which the session manager started 122 | */ 123 | public long getStartTime() { 124 | return this.startTime; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/AppDef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | import io.undertow.predicate.Predicate; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.regex.Pattern; 23 | 24 | import ninja.siden.AssetsCustomizer; 25 | import ninja.siden.ExceptionalRoute; 26 | import ninja.siden.Filter; 27 | import ninja.siden.HttpMethod; 28 | import ninja.siden.RendererCustomizer; 29 | import ninja.siden.Route; 30 | import ninja.siden.RoutingCustomizer; 31 | import ninja.siden.WebSocketFactory; 32 | import ninja.siden.internal.PathPredicate; 33 | 34 | import org.xnio.OptionMap; 35 | 36 | /** 37 | * @author taichi 38 | */ 39 | public class AppDef { 40 | 41 | OptionMap config; 42 | 43 | List assets = new ArrayList<>(); 44 | 45 | List router = new ArrayList<>(); 46 | 47 | List errorRouter = new ArrayList<>(); 48 | 49 | List> exceptionRouter = new ArrayList<>(); 50 | 51 | List subapp = new ArrayList<>(); 52 | 53 | List websockets = new ArrayList<>(); 54 | 55 | List filters = new ArrayList<>(); 56 | 57 | public AppDef(OptionMap config) { 58 | this.config = config; 59 | } 60 | 61 | public OptionMap config() { 62 | return this.config; 63 | } 64 | 65 | public AssetsCustomizer add(String path, String root) { 66 | AssetDef def = new AssetDef(path, root); 67 | this.assets.add(def); 68 | return def; 69 | } 70 | 71 | public RoutingCustomizer add(HttpMethod method, String path, Route route) { 72 | return this.add(path, new PathPredicate(path), method, route); 73 | } 74 | 75 | public RoutingCustomizer add(HttpMethod method, Pattern p, Route route) { 76 | return this.add(p.pattern(), new PathPredicate(p), method, route); 77 | } 78 | 79 | public RoutingCustomizer add(String template, PathPredicate path, 80 | HttpMethod method, Route route) { 81 | RoutingDef def = new RoutingDef(template, path, method, route); 82 | this.router.add(def); 83 | return def; 84 | } 85 | 86 | public void add(String template, Predicate predicate, 87 | WebSocketFactory factory) { 88 | this.websockets.add(new WebSocketDef(template, predicate, factory)); 89 | } 90 | 91 | public void add(Predicate predicate, Filter filter) { 92 | this.filters.add(new FilterDef(predicate, filter)); 93 | } 94 | 95 | public void add(String prefix, AppDef subapp) { 96 | this.subapp.add(new SubAppDef(prefix, subapp)); 97 | } 98 | 99 | public RendererCustomizer add(Class type, 100 | ExceptionalRoute route) { 101 | ExceptionalRoutingDef def = new ExceptionalRoutingDef<>(type, route); 102 | this.exceptionRouter.add(def); 103 | return def; 104 | } 105 | 106 | public RendererCustomizer add(int errorCode, Route route) { 107 | ErrorCodeRoutingDef def = new ErrorCodeRoutingDef(errorCode, route); 108 | this.errorRouter.add(def); 109 | return def; 110 | } 111 | 112 | public void accept(AppContext context, AppBuilder ab) { 113 | this.assets.forEach(d -> ab.apply(context, d)); 114 | this.router.forEach(d -> ab.apply(context, d)); 115 | this.errorRouter.forEach(d -> ab.apply(context, d)); 116 | this.exceptionRouter.forEach(d -> ab.apply(context, d)); 117 | this.subapp.forEach(d -> ab.apply(context, d)); 118 | this.websockets.forEach(d -> ab.apply(context, d)); 119 | this.filters.forEach(d -> ab.apply(context, d)); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/SidenSession.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.server.HttpServerExchange; 19 | import io.undertow.server.session.SessionConfig; 20 | 21 | import java.text.SimpleDateFormat; 22 | import java.util.Date; 23 | import java.util.Formatter; 24 | import java.util.Iterator; 25 | import java.util.Optional; 26 | 27 | import ninja.siden.AttributeContainer; 28 | import ninja.siden.Session; 29 | 30 | /** 31 | * @author taichi 32 | */ 33 | public class SidenSession implements Session { 34 | 35 | final HttpServerExchange exchange; 36 | final io.undertow.server.session.Session delegate; 37 | 38 | public SidenSession(HttpServerExchange exchange, 39 | io.undertow.server.session.Session delegate) { 40 | this.exchange = exchange; 41 | this.delegate = delegate; 42 | } 43 | 44 | @Override 45 | @SuppressWarnings("unchecked") 46 | public Optional attr(String key, T newone) { 47 | return Optional.ofNullable((T) this.delegate.setAttribute(key, newone)); 48 | } 49 | 50 | @Override 51 | @SuppressWarnings("unchecked") 52 | public Optional attr(String key) { 53 | return Optional.ofNullable((T) this.delegate.getAttribute(key)); 54 | } 55 | 56 | @Override 57 | @SuppressWarnings("unchecked") 58 | public Optional remove(String key) { 59 | return Optional.ofNullable((T) this.delegate.removeAttribute(key)); 60 | } 61 | 62 | @Override 63 | public Iterator iterator() { 64 | Iterator names = this.delegate.getAttributeNames().iterator(); 65 | return new Iterator() { 66 | @Override 67 | public boolean hasNext() { 68 | return names.hasNext(); 69 | } 70 | 71 | @Override 72 | public AttributeContainer.Attr next() { 73 | String name = names.next(); 74 | return new AttributeContainer.Attr() { 75 | 76 | @Override 77 | public T value() { 78 | Optional o = SidenSession.this.attr(name); 79 | return o.get(); 80 | } 81 | 82 | @Override 83 | public T remove() { 84 | Optional o = SidenSession.this.remove(name); 85 | return o.get(); 86 | } 87 | 88 | @Override 89 | public String name() { 90 | return name; 91 | } 92 | }; 93 | } 94 | }; 95 | } 96 | 97 | @Override 98 | public String id() { 99 | return this.delegate.getId(); 100 | } 101 | 102 | @Override 103 | public void invalidate() { 104 | this.delegate.invalidate(this.exchange); 105 | } 106 | 107 | @Override 108 | public Session regenerate() { 109 | SessionConfig config = this.exchange 110 | .getAttachment(SessionConfig.ATTACHMENT_KEY); 111 | this.delegate.changeSessionId(this.exchange, config); 112 | return this; 113 | } 114 | 115 | @Override 116 | public io.undertow.server.session.Session raw() { 117 | return this.delegate; 118 | } 119 | 120 | @SuppressWarnings("resource") 121 | @Override 122 | public String toString() { 123 | Formatter fmt = new Formatter(); 124 | fmt.format("Session{ id:%s", id()); 125 | SimpleDateFormat sdf = new SimpleDateFormat(); 126 | fmt.format(",CreatationTime:%s", 127 | sdf.format(new Date(raw().getCreationTime()))); 128 | fmt.format(",LastAccessedTime:%s", 129 | sdf.format(new Date(raw().getLastAccessedTime()))); 130 | fmt.format(",values:["); 131 | forEach(a -> fmt.format(" %s=%s", a.name(), a.value())); 132 | fmt.format("]}"); 133 | 134 | return fmt.toString(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/internal/PathPredicateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertFalse; 20 | import static org.junit.Assert.assertTrue; 21 | import io.undertow.server.HttpServerExchange; 22 | 23 | import java.util.Map; 24 | import java.util.regex.Pattern; 25 | 26 | import mockit.Mock; 27 | import mockit.MockUp; 28 | import mockit.integration.junit4.JMockit; 29 | 30 | import org.junit.BeforeClass; 31 | import org.junit.Test; 32 | import org.junit.runner.RunWith; 33 | 34 | /** 35 | * @author taichi 36 | */ 37 | @RunWith(JMockit.class) 38 | public class PathPredicateTest { 39 | 40 | @BeforeClass 41 | public static void beforeClass() throws Exception { 42 | Testing.useALL(PathPredicate.class); 43 | } 44 | 45 | HttpServerExchange exchange; 46 | 47 | void setUpRequest(String path) { 48 | this.exchange = new MockUp() { 49 | @Mock(invocations = 1) 50 | public String getRelativePath() { 51 | return path; 52 | } 53 | }.getMockInstance(); 54 | this.exchange.setRelativePath(path); 55 | } 56 | 57 | @Test 58 | public void matchCompletely() throws Exception { 59 | setUpRequest("/foo/bar"); 60 | 61 | PathPredicate target = new PathPredicate("/foo/bar"); 62 | assertTrue(target.resolve(this.exchange)); 63 | } 64 | 65 | @Test 66 | public void matchPartial() throws Exception { 67 | setUpRequest("/foo/bar/baz"); 68 | 69 | PathPredicate target = new PathPredicate("/foo/bar"); 70 | assertTrue(target.resolve(this.exchange)); 71 | } 72 | 73 | @Test 74 | public void unmatchPartial() throws Exception { 75 | setUpRequest("/foo/baz/bar"); 76 | 77 | PathPredicate target = new PathPredicate("/foo/bar"); 78 | assertFalse(target.resolve(this.exchange)); 79 | } 80 | 81 | @Test 82 | public void matchWithOneVars() throws Exception { 83 | setUpRequest("/foo/aaa"); 84 | 85 | PathPredicate target = new PathPredicate("/foo/:bar"); 86 | assertTrue(target.resolve(this.exchange)); 87 | Map m = this.exchange 88 | .getAttachment(PathPredicate.PARAMS); 89 | assertEquals("aaa", m.get("bar")); 90 | } 91 | 92 | @Test 93 | public void matchWithDots() throws Exception { 94 | setUpRequest("/foo/aaa.json"); 95 | 96 | PathPredicate target = new PathPredicate("/foo/:bar.:ext"); 97 | assertTrue(target.resolve(this.exchange)); 98 | Map m = this.exchange 99 | .getAttachment(PathPredicate.PARAMS); 100 | assertEquals("aaa", m.get("bar")); 101 | assertEquals("json", m.get("ext")); 102 | } 103 | 104 | @Test 105 | public void matchWithMultiVars() throws Exception { 106 | setUpRequest("/foo/aaa/baz/ccc"); 107 | 108 | PathPredicate target = new PathPredicate("/foo/:bar/baz/:foo"); 109 | assertTrue(target.resolve(this.exchange)); 110 | Map m = this.exchange 111 | .getAttachment(PathPredicate.PARAMS); 112 | 113 | assertEquals("aaa", m.get("bar")); 114 | assertEquals("ccc", m.get("foo")); 115 | } 116 | 117 | @Test 118 | public void matchWithMultiVarsByRegex() throws Exception { 119 | setUpRequest("/foo/aaa/baz/ccc"); 120 | 121 | PathPredicate target = new PathPredicate( 122 | Pattern.compile("/foo/(?\\w+)/baz/(?\\w+)")); 123 | assertTrue(target.resolve(this.exchange)); 124 | 125 | Map m = this.exchange 126 | .getAttachment(PathPredicate.PARAMS); 127 | assertEquals("aaa", m.get("bar")); 128 | assertEquals("ccc", m.get("foo")); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/RoutingDef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | import io.undertow.predicate.Predicate; 19 | import io.undertow.predicate.Predicates; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Collections; 23 | import java.util.List; 24 | import java.util.Objects; 25 | 26 | import ninja.siden.HttpMethod; 27 | import ninja.siden.Renderer; 28 | import ninja.siden.Request; 29 | import ninja.siden.Route; 30 | import ninja.siden.RoutingCustomizer; 31 | import ninja.siden.internal.Core; 32 | import ninja.siden.internal.MIMEPredicate; 33 | import ninja.siden.internal.RoutingHandler; 34 | 35 | import org.xnio.OptionMap; 36 | 37 | /** 38 | * @author taichi 39 | */ 40 | public class RoutingDef implements RoutingCustomizer { 41 | 42 | final String template; 43 | final Predicate predicate; 44 | final HttpMethod method; 45 | final Route route; 46 | Renderer renderer; 47 | String type = ""; 48 | List accepts = new ArrayList<>(); 49 | Predicate matches = Predicates.truePredicate(); 50 | 51 | public RoutingDef(String template, Predicate predicate, HttpMethod method, 52 | Route route) { 53 | this.template = template; 54 | this.predicate = predicate; 55 | this.method = method; 56 | this.route = route; 57 | } 58 | 59 | @Override 60 | public RoutingCustomizer type(String type) { 61 | this.type = Objects.requireNonNull(type); 62 | return this; 63 | } 64 | 65 | @Override 66 | public RoutingCustomizer accept(String type) { 67 | this.accepts.add(Objects.requireNonNull(type)); 68 | return this; 69 | } 70 | 71 | @Override 72 | public RoutingCustomizer match(java.util.function.Predicate fn) { 73 | this.matches = Predicates.and(this.matches, Core.adapt(fn)); 74 | return this; 75 | } 76 | 77 | @Override 78 | public RoutingCustomizer render(Renderer renderer) { 79 | this.renderer = renderer; 80 | return this; 81 | } 82 | 83 | public void addTo(RoutingHandler rh, OptionMap config) { 84 | List list = new ArrayList<>(); 85 | list.add(this.predicate); 86 | list.add(this.matches); 87 | Route route = this.route; 88 | if (this.type != null && this.type.isEmpty() == false) { 89 | list.add(MIMEPredicate.accept(type)); 90 | route = (req, res) -> { 91 | Object result = this.route.handle(req, res); 92 | res.type(this.type); 93 | return result; 94 | }; 95 | } 96 | this.accepts.stream().filter(s -> s.isEmpty() == false) 97 | .map(s -> MIMEPredicate.contentType(s)).forEach(list::add); 98 | rh.add(Predicates.and(list.toArray(new Predicate[list.size()])), route, 99 | this.renderer); 100 | } 101 | 102 | public String template() { 103 | return this.template; 104 | } 105 | 106 | public Predicate predicate() { 107 | return this.predicate; 108 | } 109 | 110 | public HttpMethod method() { 111 | return this.method; 112 | } 113 | 114 | public Route route() { 115 | return this.route; 116 | } 117 | 118 | public Renderer renderer() { 119 | return this.renderer; 120 | } 121 | 122 | public String type() { 123 | return this.type; 124 | } 125 | 126 | public List accepts() { 127 | return Collections.unmodifiableList(this.accepts); 128 | } 129 | 130 | public void acceps(List accepts) { 131 | this.accepts = Objects.requireNonNull(accepts); 132 | } 133 | 134 | public Predicate matches() { 135 | return this.matches; 136 | } 137 | 138 | public void matches(Predicate matches) { 139 | this.matches = Objects.requireNonNull(matches); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/SidenCookie.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import java.util.Date; 19 | import java.util.Formatter; 20 | 21 | import ninja.siden.Cookie; 22 | 23 | /** 24 | * @author taichi 25 | */ 26 | public class SidenCookie implements Cookie { 27 | 28 | final io.undertow.server.handlers.Cookie delegate; 29 | 30 | SidenCookie(io.undertow.server.handlers.Cookie delegate) { 31 | this.delegate = delegate; 32 | } 33 | 34 | @Override 35 | public String name() { 36 | return this.delegate.getName(); 37 | } 38 | 39 | @Override 40 | public String value() { 41 | return this.delegate.getValue(); 42 | } 43 | 44 | @Override 45 | public Cookie value(String value) { 46 | this.delegate.setValue(value); 47 | return this; 48 | } 49 | 50 | @Override 51 | public String path() { 52 | return this.delegate.getPath(); 53 | } 54 | 55 | @Override 56 | public Cookie path(String path) { 57 | this.delegate.setPath(path); 58 | return this; 59 | } 60 | 61 | @Override 62 | public String domain() { 63 | return this.delegate.getDomain(); 64 | } 65 | 66 | @Override 67 | public Cookie domain(String domain) { 68 | this.delegate.setDomain(domain); 69 | return this; 70 | } 71 | 72 | @Override 73 | public Integer maxAge() { 74 | return this.delegate.getMaxAge(); 75 | } 76 | 77 | @Override 78 | public Cookie maxAge(Integer maxAge) { 79 | this.delegate.setMaxAge(maxAge); 80 | return this; 81 | } 82 | 83 | @Override 84 | public boolean discard() { 85 | return this.delegate.isDiscard(); 86 | } 87 | 88 | @Override 89 | public Cookie discard(boolean discard) { 90 | this.delegate.setDiscard(discard); 91 | return this; 92 | } 93 | 94 | @Override 95 | public boolean secure() { 96 | return this.delegate.isSecure(); 97 | } 98 | 99 | @Override 100 | public Cookie secure(boolean secure) { 101 | this.delegate.setSecure(secure); 102 | return this; 103 | } 104 | 105 | @Override 106 | public int version() { 107 | return this.delegate.getVersion(); 108 | } 109 | 110 | @Override 111 | public Cookie version(int version) { 112 | this.delegate.setVersion(version); 113 | return this; 114 | } 115 | 116 | @Override 117 | public boolean httpOnly() { 118 | return this.delegate.isHttpOnly(); 119 | } 120 | 121 | @Override 122 | public Cookie httpOnly(boolean httpOnly) { 123 | this.delegate.setHttpOnly(httpOnly); 124 | return this; 125 | } 126 | 127 | @Override 128 | public Date expires() { 129 | return this.delegate.getExpires(); 130 | } 131 | 132 | @Override 133 | public Cookie expires(Date expires) { 134 | this.delegate.setExpires(expires); 135 | return this; 136 | } 137 | 138 | @Override 139 | public String comment() { 140 | return this.delegate.getComment(); 141 | } 142 | 143 | @Override 144 | public Cookie comment(String comment) { 145 | this.delegate.setComment(comment); 146 | return this; 147 | } 148 | 149 | @Override 150 | @SuppressWarnings("resource") 151 | public String toString() { 152 | Formatter fmt = new Formatter(); 153 | fmt.format("Cookie{name:%s", name()); 154 | fmt.format(" ,value:%s", value()); 155 | fmt.format(" ,path:%s", path()); 156 | fmt.format(" ,domain:%s", domain()); 157 | fmt.format(" ,maxAge:%d", maxAge()); 158 | fmt.format(" ,discard:%s", discard()); 159 | fmt.format(" ,secure:%s", secure()); 160 | fmt.format(" ,version:%s", version()); 161 | fmt.format(" ,httpOnly:%s", httpOnly()); 162 | fmt.format(" ,expires:%s", expires()); 163 | fmt.format(" ,comment:%s}", comment()); 164 | return fmt.toString(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/def/AssetDef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.def; 17 | 18 | import io.undertow.io.IoCallback; 19 | import io.undertow.predicate.Predicate; 20 | import io.undertow.predicate.Predicates; 21 | import io.undertow.server.handlers.PathHandler; 22 | import io.undertow.server.handlers.resource.ClassPathResourceManager; 23 | import io.undertow.server.handlers.resource.FileResourceManager; 24 | import io.undertow.server.handlers.resource.ResourceHandler; 25 | import io.undertow.server.handlers.resource.ResourceManager; 26 | import io.undertow.server.handlers.resource.URLResource; 27 | import io.undertow.util.Headers; 28 | 29 | import java.io.File; 30 | import java.net.URL; 31 | import java.util.Objects; 32 | 33 | import ninja.siden.AssetsCustomizer; 34 | import ninja.siden.Config; 35 | 36 | import org.xnio.OptionMap; 37 | 38 | /** 39 | * @author taichi 40 | */ 41 | public class AssetDef implements AssetsCustomizer { 42 | 43 | final String path; 44 | 45 | final String root; 46 | 47 | Integer cacheTime; 48 | 49 | boolean directoryListing = false; 50 | 51 | boolean canonicalizePaths = true; 52 | 53 | Predicate cachable = Predicates.truePredicate(); 54 | 55 | Predicate allowed = Predicates.truePredicate(); 56 | 57 | String[] welcomeFiles; 58 | 59 | ClassLoader loadFrom; 60 | 61 | public AssetDef(String path, String root) { 62 | this.path = Objects.requireNonNull(path); 63 | this.root = Objects.requireNonNull(root); 64 | } 65 | 66 | @Override 67 | public AssetsCustomizer cacheTime(Integer time) { 68 | this.cacheTime = Objects.requireNonNull(time); 69 | return this; 70 | } 71 | 72 | @Override 73 | public AssetsCustomizer directoryListing(boolean is) { 74 | this.directoryListing = is; 75 | return this; 76 | } 77 | 78 | public AssetsCustomizer setCanonicalizePaths(boolean canonicalizePaths) { 79 | this.canonicalizePaths = canonicalizePaths; 80 | return this; 81 | } 82 | 83 | @Override 84 | public AssetsCustomizer welcomeFiles(String... files) { 85 | this.welcomeFiles = Objects.requireNonNull(files); 86 | return this; 87 | } 88 | 89 | @Override 90 | public AssetsCustomizer from(ClassLoader loader) { 91 | this.loadFrom = Objects.requireNonNull(loader); 92 | return this; 93 | } 94 | 95 | public void addTo(PathHandler ph, OptionMap config) { 96 | ResourceHandler rh = new ResourceHandler(newResourceManager(config)); 97 | rh.setMimeMappings(config.get(Config.MIME_MAPPINGS)); 98 | if (this.cacheTime != null) { 99 | rh.setCacheTime(this.cacheTime); 100 | } 101 | rh.setDirectoryListingEnabled(this.directoryListing); 102 | rh.setCanonicalizePaths(this.canonicalizePaths); 103 | if (this.welcomeFiles != null) { 104 | rh.setWelcomeFiles(this.welcomeFiles); 105 | } 106 | 107 | ph.addPrefixPath(this.path, rh); 108 | } 109 | 110 | protected ResourceManager newResourceManager(OptionMap config) { 111 | if (this.loadFrom == null) { 112 | return new FileResourceManager(new File(this.root), 113 | config.get(Config.TRANSFER_MIN_SIZE)); 114 | } 115 | return new ClassPathResourceManager(this.loadFrom, this.root); 116 | } 117 | 118 | public static void useDefaultFavicon(PathHandler ph) { 119 | ph.addExactPath( 120 | "/favicon.ico", 121 | ex -> { 122 | URL url = AssetDef.class.getClassLoader().getResource( 123 | "favicon.ico"); 124 | URLResource resource = new URLResource(url, url 125 | .openConnection(), url.getPath()); 126 | ex.getResponseHeaders().put(Headers.CONTENT_TYPE, 127 | "image/x-icon"); 128 | resource.serve(ex.getResponseSender(), ex, 129 | IoCallback.END_EXCHANGE); 130 | }); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/SidenResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import io.undertow.server.HttpServerExchange; 19 | import io.undertow.util.DateUtils; 20 | import io.undertow.util.HeaderMap; 21 | import io.undertow.util.Headers; 22 | import io.undertow.util.HttpString; 23 | import io.undertow.util.StatusCodes; 24 | 25 | import java.util.Arrays; 26 | import java.util.Date; 27 | import java.util.Formatter; 28 | import java.util.Map; 29 | 30 | import ninja.siden.Config; 31 | import ninja.siden.Cookie; 32 | import ninja.siden.Renderer; 33 | import ninja.siden.RendererRepository; 34 | import ninja.siden.Response; 35 | import ninja.siden.util.Suppress; 36 | 37 | import org.xnio.OptionMap; 38 | 39 | /** 40 | * @author taichi 41 | */ 42 | public class SidenResponse implements Response { 43 | 44 | final HttpServerExchange exchange; 45 | 46 | public SidenResponse(HttpServerExchange exchange) { 47 | this.exchange = exchange; 48 | } 49 | 50 | @Override 51 | public Response status(int code) { 52 | this.exchange.setResponseCode(code); 53 | return this; 54 | } 55 | 56 | @Override 57 | public Response header(String name, String... values) { 58 | HeaderMap hm = this.exchange.getResponseHeaders(); 59 | hm.remove(name); 60 | hm.addAll(new HttpString(name), Arrays.asList(values)); 61 | return this; 62 | } 63 | 64 | @Override 65 | public Response header(String name, long date) { 66 | this.exchange.getResponseHeaders().put(new HttpString(name), 67 | DateUtils.toDateString(new Date(date))); 68 | return this; 69 | } 70 | 71 | @Override 72 | public Response headers(Map headers) { 73 | HeaderMap hm = this.exchange.getResponseHeaders(); 74 | headers.forEach((k, v) -> { 75 | hm.put(new HttpString(k), v); 76 | }); 77 | return this; 78 | } 79 | 80 | @Override 81 | public Cookie cookie(String name, String value) { 82 | io.undertow.server.handlers.Cookie c = new io.undertow.server.handlers.CookieImpl( 83 | name, value); 84 | this.exchange.setResponseCookie(c); 85 | return new SidenCookie(c); 86 | } 87 | 88 | @Override 89 | public Cookie removeCookie(String name) { 90 | return new SidenCookie(this.exchange.getResponseCookies().remove(name)); 91 | } 92 | 93 | @Override 94 | public Response type(String contentType) { 95 | this.exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, 96 | contentType); 97 | return this; 98 | } 99 | 100 | @Override 101 | public HttpServerExchange raw() { 102 | return this.exchange; 103 | } 104 | 105 | @Override 106 | public ExchangeState redirect(String location) { 107 | return this.redirect(StatusCodes.FOUND, location); 108 | } 109 | 110 | @Override 111 | public ExchangeState redirect(int code, String location) { 112 | this.exchange.setResponseCode(code); 113 | this.exchange.getResponseHeaders().put(Headers.LOCATION, location); 114 | this.exchange.endExchange(); 115 | return ExchangeState.Redirected; 116 | } 117 | 118 | @Override 119 | public ExchangeState render(MODEL model, Renderer renderer) { 120 | return Suppress.get(() -> { 121 | renderer.render(model, this.exchange); 122 | return ExchangeState.Rendered; 123 | }); 124 | } 125 | 126 | @Override 127 | public ExchangeState render(MODEL model, String template) { 128 | OptionMap config = this.exchange.getAttachment(Core.CONFIG); 129 | RendererRepository repo = config.get(Config.RENDERER_REPOSITORY); 130 | return render(model, repo.find(template)); 131 | } 132 | 133 | @Override 134 | @SuppressWarnings("resource") 135 | public String toString() { 136 | Formatter fmt = new Formatter(); 137 | fmt.format("RESPONSE{ResponseCode:%d", this.exchange.getResponseCode()); 138 | fmt.format(" ,Headers:[%s]", this.exchange.getResponseHeaders()); 139 | fmt.format(" ,Cookies:["); 140 | this.exchange.getResponseCookies().forEach((k, c) -> { 141 | fmt.format("%s", new SidenCookie(c)); 142 | }); 143 | return fmt.format("]}").toString(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/util/Trial.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.util; 17 | 18 | import java.util.Optional; 19 | import java.util.function.Function; 20 | import java.util.function.Predicate; 21 | import java.util.function.Supplier; 22 | 23 | /** 24 | * @author taichi 25 | */ 26 | public interface Trial { 27 | 28 | boolean success(); 29 | 30 | boolean failure(); 31 | 32 | U either(Function onSuccess, 33 | Function onFailure); 34 | 35 | Optional> filter(Predicate predicate); 36 | 37 | T get(); 38 | 39 | T orElse(T other); 40 | 41 | T orElseGet(Supplier other); 42 | 43 | Trial map(Function mapper); 44 | 45 | Trial flatMap(Function> mapper); 46 | 47 | static class Success implements Trial { 48 | final T value; 49 | 50 | Success(T value) { 51 | this.value = value; 52 | } 53 | 54 | @Override 55 | public boolean success() { 56 | return true; 57 | } 58 | 59 | @Override 60 | public boolean failure() { 61 | return false; 62 | } 63 | 64 | @Override 65 | public U either(Function onSuccess, 66 | Function onFailure) { 67 | return onSuccess.apply(this.value); 68 | } 69 | 70 | @Override 71 | public Optional> filter(Predicate predicate) { 72 | return predicate.test(this.value) ? Optional.of(this) : Optional 73 | .empty(); 74 | } 75 | 76 | @Override 77 | public T get() { 78 | return this.value; 79 | } 80 | 81 | @Override 82 | public T orElse(T other) { 83 | return this.value; 84 | } 85 | 86 | @Override 87 | public T orElseGet(Supplier other) { 88 | return this.value; 89 | } 90 | 91 | @Override 92 | public Trial map(Function mapper) { 93 | return new Success(mapper.apply(this.value)); 94 | } 95 | 96 | @Override 97 | public Trial flatMap(Function> mapper) { 98 | return mapper.apply(this.value); 99 | } 100 | } 101 | 102 | static class Failure implements Trial { 103 | static class FailureException extends RuntimeException { 104 | private static final long serialVersionUID = 6537304884970413146L; 105 | 106 | public FailureException(Exception exception) { 107 | super(exception); 108 | } 109 | } 110 | 111 | final Exception exception; 112 | 113 | Failure(Exception exception) { 114 | this.exception = exception; 115 | } 116 | 117 | @Override 118 | public boolean success() { 119 | return false; 120 | } 121 | 122 | @Override 123 | public boolean failure() { 124 | return true; 125 | } 126 | 127 | @Override 128 | public U either(Function onSuccess, 129 | Function onFailure) { 130 | return onFailure.apply(this.exception); 131 | } 132 | 133 | @Override 134 | public Optional> filter(Predicate predicate) { 135 | return Optional.empty(); 136 | } 137 | 138 | @Override 139 | public T get() { 140 | throw new FailureException(this.exception); 141 | } 142 | 143 | @Override 144 | public T orElse(T other) { 145 | return other; 146 | } 147 | 148 | @Override 149 | public T orElseGet(Supplier other) { 150 | return other.get(); 151 | } 152 | 153 | @Override 154 | public Trial map(Function mapper) { 155 | return new Failure(this.exception); 156 | } 157 | 158 | @Override 159 | public Trial flatMap(Function> mapper) { 160 | return new Failure(exception); 161 | } 162 | } 163 | 164 | public static Function> of( 165 | ExceptionalFunction fn) { 166 | return t -> { 167 | try { 168 | return new Success(fn.apply(t)); 169 | } catch (Exception e) { 170 | return new Failure(e); 171 | } 172 | }; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /siden-core/src/test/java/ninja/siden/jmx/MetricsAppBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import java.lang.management.ManagementFactory; 21 | 22 | import javax.management.MBeanAttributeInfo; 23 | import javax.management.MBeanInfo; 24 | import javax.management.MBeanServer; 25 | import javax.management.ObjectName; 26 | 27 | import ninja.siden.App; 28 | import ninja.siden.Config; 29 | import ninja.siden.Stoppable; 30 | import ninja.siden.internal.Testing; 31 | 32 | import org.junit.After; 33 | import org.junit.Before; 34 | import org.junit.BeforeClass; 35 | import org.junit.Test; 36 | 37 | /** 38 | * @author taichi 39 | */ 40 | public class MetricsAppBuilderTest { 41 | 42 | @BeforeClass 43 | public static void beforeClass() throws Exception { 44 | Testing.useALL(App.class); 45 | } 46 | 47 | MBeanServer server; 48 | App target; 49 | Stoppable stopper; 50 | 51 | @Before 52 | public void setUp() throws Exception { 53 | this.server = ManagementFactory.getPlatformMBeanServer(); 54 | this.target = App.configure(b -> b.set(Config.ENV, "prod")); 55 | } 56 | 57 | @After 58 | public void tearDown() throws Exception { 59 | this.stopper.stop(); 60 | } 61 | 62 | static int port = 9000; 63 | 64 | protected void listen() { 65 | this.stopper = this.target.listen(port++); 66 | } 67 | 68 | @Test 69 | public void session() throws Exception { 70 | this.listen(); 71 | 72 | ObjectName on = new ObjectName("ninja.siden:type=Session"); 73 | MBeanInfo info = server.getMBeanInfo(on); 74 | MBeanAttributeInfo attr = info.getAttributes()[0]; 75 | assertEquals("Metrics", attr.getName()); 76 | } 77 | 78 | @Test 79 | public void global() throws Exception { 80 | this.listen(); 81 | 82 | ObjectName on = new ObjectName("ninja.siden:type=Request,name=Global"); 83 | MBeanInfo info = server.getMBeanInfo(on); 84 | MBeanAttributeInfo attr = info.getAttributes()[0]; 85 | assertEquals("Metrics", attr.getName()); 86 | } 87 | 88 | @Test 89 | public void routes() throws Exception { 90 | target.get("/aaa", (req, res) -> "abc"); 91 | this.listen(); 92 | 93 | ObjectName on = new ObjectName( 94 | "ninja.siden:type=Request,path=\"/aaa\",method=GET"); 95 | MBeanInfo info = server.getMBeanInfo(on); 96 | MBeanAttributeInfo attr = info.getAttributes()[0]; 97 | assertEquals("Metrics", attr.getName()); 98 | } 99 | 100 | @Test 101 | public void nestedRoutes() throws Exception { 102 | App sub = new App(); 103 | sub.head("/def", (req, res) -> "def"); 104 | target.use("/abc", sub); 105 | this.listen(); 106 | ObjectName abc = new ObjectName( 107 | "ninja.siden:type=Request,path=\"/abc/def\",method=HEAD"); 108 | server.getMBeanInfo(abc); 109 | } 110 | 111 | @Test 112 | public void nestedRoutesTwoTimes() throws Exception { 113 | App sub = new App(); 114 | sub.head("/def", (req, res) -> "def"); 115 | target.use("/abc", sub); 116 | target.use("/efg", sub); 117 | this.listen(); 118 | 119 | ObjectName abc = new ObjectName( 120 | "ninja.siden:type=Request,path=\"/abc/def\",method=HEAD"); 121 | server.getMBeanInfo(abc); 122 | 123 | ObjectName efg = new ObjectName( 124 | "ninja.siden:type=Request,path=\"/efg/def\",method=HEAD"); 125 | server.getMBeanInfo(efg); 126 | } 127 | 128 | @Test 129 | public void deeplyNestedRoutes() throws Exception { 130 | App subsub = new App(); 131 | subsub.get("/jkl", (req, res) -> "eee"); 132 | App sub = new App(); 133 | sub.head("/def", (req, res) -> "def"); 134 | sub.use("/ghi", subsub); 135 | 136 | target.use("/abc", sub); 137 | this.listen(); 138 | 139 | ObjectName def = new ObjectName( 140 | "ninja.siden:type=Request,path=\"/abc/def\",method=HEAD"); 141 | server.getMBeanInfo(def); 142 | 143 | ObjectName jkl = new ObjectName( 144 | "ninja.siden:type=Request,path=\"/abc/ghi/jkl\",method=GET"); 145 | server.getMBeanInfo(jkl); 146 | } 147 | 148 | @Test 149 | public void websockets() throws Exception { 150 | App sub = new App(); 151 | sub.websocket("/ws").onText((c, s) -> c.send(s)); 152 | target.use("/aaa", sub); 153 | this.listen(); 154 | 155 | ObjectName ws = new ObjectName( 156 | "ninja.siden:type=WebSocket,path=\"/aaa/ws\""); 157 | server.getMBeanInfo(ws); 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/internal/LambdaWebSocketFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.internal; 17 | 18 | import java.nio.ByteBuffer; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import ninja.siden.Connection; 23 | import ninja.siden.WebSocket; 24 | import ninja.siden.WebSocketCustomizer; 25 | import ninja.siden.WebSocketFactory; 26 | import ninja.siden.util.ExceptionalBiConsumer; 27 | import ninja.siden.util.ExceptionalConsumer; 28 | 29 | /** 30 | * @author taichi 31 | */ 32 | public class LambdaWebSocketFactory implements WebSocketFactory, 33 | WebSocketCustomizer { 34 | 35 | List> conn = new ArrayList<>(); 36 | List> txt = new ArrayList<>(); 37 | List> bin = new ArrayList<>(); 38 | List> pong = new ArrayList<>(); 39 | List> ping = new ArrayList<>(); 40 | List> close = new ArrayList<>(); 41 | 42 | @Override 43 | public WebSocket create(Connection connection) { 44 | return new LambdaWebSocket(); 45 | } 46 | 47 | class LambdaWebSocket implements WebSocket { 48 | Connection connection; 49 | 50 | List> conn = new ArrayList<>( 51 | LambdaWebSocketFactory.this.conn); 52 | List> txt = new ArrayList<>( 53 | LambdaWebSocketFactory.this.txt); 54 | List> bin = new ArrayList<>( 55 | LambdaWebSocketFactory.this.bin); 56 | List> pong = new ArrayList<>( 57 | LambdaWebSocketFactory.this.pong); 58 | List> ping = new ArrayList<>( 59 | LambdaWebSocketFactory.this.ping); 60 | List> close = new ArrayList<>( 61 | LambdaWebSocketFactory.this.close); 62 | 63 | @Override 64 | public void onConnect(Connection connection) throws Exception { 65 | this.connection = connection; 66 | for (ExceptionalConsumer fn : this.conn) { 67 | fn.accept(this.connection); 68 | } 69 | } 70 | 71 | void forEach(T payload, 72 | List> list) 73 | throws Exception { 74 | for (ExceptionalBiConsumer fn : list) { 75 | fn.accept(this.connection, payload); 76 | } 77 | } 78 | 79 | @Override 80 | public void onText(String payload) throws Exception { 81 | forEach(payload, this.txt); 82 | } 83 | 84 | @Override 85 | public void onBinary(ByteBuffer[] payload) throws Exception { 86 | forEach(payload, this.bin); 87 | } 88 | 89 | @Override 90 | public void onPong(ByteBuffer[] payload) throws Exception { 91 | forEach(payload, this.pong); 92 | } 93 | 94 | @Override 95 | public void onPing(ByteBuffer[] payload) throws Exception { 96 | forEach(payload, this.ping); 97 | } 98 | 99 | @Override 100 | public void onClose(ByteBuffer[] payload) throws Exception { 101 | forEach(payload, this.close); 102 | } 103 | } 104 | 105 | @Override 106 | public WebSocketCustomizer onConnect( 107 | ExceptionalConsumer fn) { 108 | this.conn.add(fn); 109 | return this; 110 | } 111 | 112 | @Override 113 | public WebSocketCustomizer onText( 114 | ExceptionalBiConsumer fn) { 115 | this.txt.add(fn); 116 | return this; 117 | } 118 | 119 | @Override 120 | public WebSocketCustomizer onBinary( 121 | ExceptionalBiConsumer fn) { 122 | this.bin.add(fn); 123 | return this; 124 | } 125 | 126 | @Override 127 | public WebSocketCustomizer onPong( 128 | ExceptionalBiConsumer fn) { 129 | this.pong.add(fn); 130 | return this; 131 | } 132 | 133 | @Override 134 | public WebSocketCustomizer onPing( 135 | ExceptionalBiConsumer fn) { 136 | this.ping.add(fn); 137 | return this; 138 | } 139 | 140 | @Override 141 | public WebSocketCustomizer onClose( 142 | ExceptionalBiConsumer fn) { 143 | this.close.add(fn); 144 | return this; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /siden-core/src/main/java/ninja/siden/jmx/MetricsAppBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 SATO taichi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package ninja.siden.jmx; 17 | 18 | import io.undertow.server.HttpHandler; 19 | import io.undertow.server.handlers.MetricsHandler; 20 | import io.undertow.server.session.InMemorySessionManager; 21 | import io.undertow.server.session.SessionAttachmentHandler; 22 | import io.undertow.server.session.SessionCookieConfig; 23 | 24 | import java.lang.management.ManagementFactory; 25 | import java.util.Arrays; 26 | import java.util.List; 27 | 28 | import javax.management.MBeanServer; 29 | import javax.management.ObjectName; 30 | 31 | import ninja.siden.App; 32 | import ninja.siden.Config; 33 | import ninja.siden.Route; 34 | import ninja.siden.WebSocketFactory; 35 | import ninja.siden.def.AppContext; 36 | import ninja.siden.def.DefaultAppBuilder; 37 | import ninja.siden.def.RoutingDef; 38 | import ninja.siden.def.SubAppDef; 39 | import ninja.siden.def.WebSocketDef; 40 | import ninja.siden.util.ExactlyOnceCloseable; 41 | 42 | import org.xnio.OptionMap; 43 | 44 | /** 45 | * @author taichi 46 | */ 47 | public class MetricsAppBuilder extends DefaultAppBuilder { 48 | 49 | public MetricsAppBuilder(OptionMap config) { 50 | super(config); 51 | } 52 | 53 | @Override 54 | public void apply(AppContext context, RoutingDef rd) { 55 | RoutingDef def = new RoutingDef(rd.template(), rd.predicate(), 56 | rd.method(), makeRouteTracker(context, rd)); 57 | if (rd.renderer() != null) { 58 | def.render(rd.renderer()); 59 | } 60 | def.type(rd.type()); 61 | def.acceps(rd.accepts()); 62 | def.matches(rd.matches()); 63 | def.addTo(this.router, this.config); 64 | } 65 | 66 | protected Route makeRouteTracker(AppContext context, RoutingDef original) { 67 | RouteTracker tracker = new RouteTracker(original.route()); 68 | register(context.root(), tracker, Arrays.asList("type", "Request", 69 | "path", 70 | ObjectName.quote(context.prefix() + original.template()), 71 | "method", original.method().name())); 72 | return tracker; 73 | } 74 | 75 | @Override 76 | public void apply(AppContext context, SubAppDef def) { 77 | MetricsAppBuilder kids = new MetricsAppBuilder(this.config); 78 | def.app().accept(new AppContext(context, def), kids); 79 | this.subapp.addPrefixPath(def.prefix(), kids.filters); 80 | } 81 | 82 | @Override 83 | public void apply(AppContext context, WebSocketDef original) { 84 | WebSocketDef def = new WebSocketDef(original.template(), 85 | original.predicate(), makeWebSocketTracker(context, original)); 86 | def.addTo(this.websockets, this.config); 87 | } 88 | 89 | protected WebSocketFactory makeWebSocketTracker(AppContext context, 90 | WebSocketDef def) { 91 | WebSocketTracker tracker = new WebSocketTracker(def.factory()); 92 | register( 93 | context.root(), 94 | tracker, 95 | Arrays.asList("type", "WebSocket", "path", 96 | ObjectName.quote(context.prefix() + def.template()))); 97 | return tracker; 98 | } 99 | 100 | @Override 101 | protected HttpHandler makeSessionHandler(App root, OptionMap config, 102 | HttpHandler next) { 103 | InMemorySessionManager sessionManager = new InMemorySessionManager( 104 | "SessionManagerOfSiden", config.get(Config.MAX_SESSIONS)); 105 | sessionManager.setDefaultSessionTimeout(config 106 | .get(Config.DEFAULT_SESSION_TIMEOUT_SECONDS)); 107 | SessionCookieConfig sessionConfig = new SessionCookieConfig(); 108 | sessionConfig.setCookieName(config.get(Config.SESSION_COOKIE_NAME)); 109 | 110 | register(root, SessionMetrics.to(sessionManager), 111 | Arrays.asList("type", "Session")); 112 | 113 | return new SessionAttachmentHandler(next, sessionManager, sessionConfig); 114 | } 115 | 116 | @Override 117 | protected HttpHandler makeSharedHandlers(App root, OptionMap config, 118 | HttpHandler next) { 119 | HttpHandler shared = super.makeSharedHandlers(root, config, next); 120 | register(root, RequestMetrics.to(new MetricsHandler(shared)), 121 | Arrays.asList("type", "Request", "name", "Global")); 122 | return shared; 123 | } 124 | 125 | protected void register(App root, Object bean, List attrs) { 126 | try { 127 | ObjectName name = ObjectNames.to("ninja.siden", attrs); 128 | MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 129 | server.registerMBean(bean, name); 130 | ExactlyOnceCloseable ec = ExactlyOnceCloseable.wrap(() -> server 131 | .unregisterMBean(name)); 132 | root.stopOn(app -> ec.close()); 133 | } catch (Exception e) { 134 | throw new IllegalStateException(e); 135 | } 136 | } 137 | } 138 | --------------------------------------------------------------------------------