From 69af3f60d399157a8951988fa2f91d29c553fbb5 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Mon, 25 Jul 2016 21:36:50 +0300 Subject: juick-ws: websocket terminal --- juick-ws/build.gradle | 11 +++++ juick-ws/package.json | 30 ++++++++++++++ .../src/main/java/com/juick/ws/ApiController.java | 24 +++++++++++ .../main/java/com/juick/ws/StatusController.java | 9 ++++- .../java/com/juick/ws/api/WebSocketStatus.java | 16 ++++++++ .../ws/configuration/WebsocketConfiguration.java | 47 ++++++++++++++++++++++ juick-ws/src/main/static/scripts.js | 36 +++++++++++++++++ juick-ws/src/main/static/style.css | 2 + .../src/main/webapp/WEB-INF/templates/index.html | 14 +++++++ juick-ws/webpack.config.js | 29 +++++++++++++ 10 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 juick-ws/package.json create mode 100644 juick-ws/src/main/java/com/juick/ws/ApiController.java create mode 100644 juick-ws/src/main/java/com/juick/ws/api/WebSocketStatus.java create mode 100644 juick-ws/src/main/static/scripts.js create mode 100644 juick-ws/src/main/static/style.css create mode 100644 juick-ws/src/main/webapp/WEB-INF/templates/index.html create mode 100644 juick-ws/webpack.config.js diff --git a/juick-ws/build.gradle b/juick-ws/build.gradle index a46f3b9f..d20131b3 100644 --- a/juick-ws/build.gradle +++ b/juick-ws/build.gradle @@ -1,3 +1,11 @@ +plugins { + id "com.moowork.node" version "0.13" +} + +task compileFrontend(type: NpmTask) { + args = ['run', 'compile'] +} + apply plugin: 'java' apply plugin: 'war' apply plugin: 'org.akhikhl.gretty' @@ -17,10 +25,13 @@ dependencies { compile 'javax.inject:javax.inject:1' compile 'org.apache.httpcomponents:httpclient:4.5.1' compile 'org.apache.commons:commons-dbcp2:2.0' + compile 'com.mitchellbosecke:pebble-spring4:2.2.2' testCompile 'junit:junit:4.12' providedRuntime 'mysql:mysql-connector-java:5.1.39' } +compileFrontend.dependsOn 'npmInstall' +war.dependsOn 'compileFrontend' compileJava.options.encoding = 'UTF-8' gretty { diff --git a/juick-ws/package.json b/juick-ws/package.json new file mode 100644 index 00000000..aa6c7a71 --- /dev/null +++ b/juick-ws/package.json @@ -0,0 +1,30 @@ +{ + "name": "juick", + "version": "1.0.0", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "compile": "webpack --colors" + }, + "repository": { + "type": "git", + "url": "https://github.com/juick/juick.git" + }, + "license": "AGPLv3", + "devDependencies": { + "autoprefixer": "^6.3.7", + "css-loader": "^0.23.1", + "extract-text-webpack-plugin": "^1.0.1", + "globby": "^4.1.0", + "postcss-import": "^8.1.2", + "postcss-loader": "^0.9.1", + "precss": "^1.4.0", + "style-loader": "^0.13.1", + "uglify-loader": "^1.3.0", + "webpack": "^1.13.1" + }, + "dependencies": { + "normalize.css": "^4.2.0", + "terminal": "https://github.com/juick/terminal.git" + } +} diff --git a/juick-ws/src/main/java/com/juick/ws/ApiController.java b/juick-ws/src/main/java/com/juick/ws/ApiController.java new file mode 100644 index 00000000..9adda912 --- /dev/null +++ b/juick-ws/src/main/java/com/juick/ws/ApiController.java @@ -0,0 +1,24 @@ +package com.juick.ws; + +import com.juick.ws.api.WebSocketStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.inject.Inject; + +/** + * Created by vitalyster on 25.07.2016. + */ +@RestController +public class ApiController { + @Inject + WebsocketComponent wsHandler; + + @RequestMapping(value = "/api/status", method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public WebSocketStatus status() { + return new WebSocketStatus(wsHandler.clients.size()); + } +} diff --git a/juick-ws/src/main/java/com/juick/ws/StatusController.java b/juick-ws/src/main/java/com/juick/ws/StatusController.java index b212af19..9c3e2404 100644 --- a/juick-ws/src/main/java/com/juick/ws/StatusController.java +++ b/juick-ws/src/main/java/com/juick/ws/StatusController.java @@ -4,6 +4,7 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; import javax.inject.Inject; @@ -17,7 +18,11 @@ public class StatusController { WebsocketComponent wsHandler; @RequestMapping(method = RequestMethod.GET, headers = "Connection!=Upgrade", value = "/") - public String status() { - return String.format("Clients: %d", wsHandler.clients.size()); + public ModelAndView status() { + ModelAndView modelAndView = new ModelAndView(); + modelAndView.addObject("clients", wsHandler.clients.size()); + modelAndView.setViewName("index"); + return modelAndView; } + } diff --git a/juick-ws/src/main/java/com/juick/ws/api/WebSocketStatus.java b/juick-ws/src/main/java/com/juick/ws/api/WebSocketStatus.java new file mode 100644 index 00000000..ada19f89 --- /dev/null +++ b/juick-ws/src/main/java/com/juick/ws/api/WebSocketStatus.java @@ -0,0 +1,16 @@ +package com.juick.ws.api; + +/** + * Created by vitalyster on 25.07.2016. + */ +public class WebSocketStatus { + private int clientsCount; + + public WebSocketStatus(int count) { + clientsCount = count; + } + + public int getClientsCount() { + return clientsCount; + } +} diff --git a/juick-ws/src/main/java/com/juick/ws/configuration/WebsocketConfiguration.java b/juick-ws/src/main/java/com/juick/ws/configuration/WebsocketConfiguration.java index d432b19c..c25f8f42 100644 --- a/juick-ws/src/main/java/com/juick/ws/configuration/WebsocketConfiguration.java +++ b/juick-ws/src/main/java/com/juick/ws/configuration/WebsocketConfiguration.java @@ -2,6 +2,11 @@ package com.juick.ws.configuration; import com.juick.ws.WebsocketComponent; import com.juick.ws.XMPPConnection; +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.loader.Loader; +import com.mitchellbosecke.pebble.loader.ServletLoader; +import com.mitchellbosecke.pebble.spring4.PebbleViewResolver; +import com.mitchellbosecke.pebble.spring4.extension.SpringExtension; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -9,13 +14,18 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import javax.inject.Inject; +import javax.servlet.ServletContext; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -47,6 +57,36 @@ public class WebsocketConfiguration extends WebMvcConfigurationSupport implement dataSource.setUrl(env.getProperty("datasource_url")); return new JdbcTemplate(dataSource); } + @Inject + private ServletContext servletContext; + + @Bean + public Loader templateLoader(){ + return new ServletLoader(servletContext); + } + + @Bean + public SpringExtension springExtension() { + return new SpringExtension(); + } + + @Bean + public PebbleEngine pebbleEngine() { + return new PebbleEngine.Builder() + .loader(this.templateLoader()) + .extension(springExtension()) + .build(); + } + + @Bean + public ViewResolver viewResolver() { + PebbleViewResolver viewResolver = new PebbleViewResolver(); + viewResolver.setPrefix("/WEB-INF/templates/"); + viewResolver.setSuffix(".html"); + viewResolver.setPebbleEngine(pebbleEngine()); + return viewResolver; + } + @Override public RequestMappingHandlerMapping requestMappingHandlerMapping() { RequestMappingHandlerMapping mapping = super.requestMappingHandlerMapping(); @@ -57,4 +97,11 @@ public class WebsocketConfiguration extends WebMvcConfigurationSupport implement public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(wsHandler(), "/**").setAllowedOrigins("*"); } + + @Override + protected void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.setOrder(0); + registry.addResourceHandler("/scripts.js").addResourceLocations("/"); + registry.addResourceHandler("/style.css").addResourceLocations("/"); + } } diff --git a/juick-ws/src/main/static/scripts.js b/juick-ws/src/main/static/scripts.js new file mode 100644 index 00000000..2691816d --- /dev/null +++ b/juick-ws/src/main/static/scripts.js @@ -0,0 +1,36 @@ +var Terminal = require('terminal'); +require('whatwg-fetch'); + +function ready(fn) { + if (document.readyState != 'loading') { + fn(); + } else { + document.addEventListener('DOMContentLoaded', fn); + } +} + +ready(function() { + var ws = new WebSocket('wss://ws.juick.com/_replies'); + var term = new Terminal('terminal', {}, {}); + var status = document.querySelector("#status"); + ws.onopen = function() { + term.output("
connected"); + }; + ws.onclose = function() { + term.output("
disconnected"); + } + ws.onmessage = function(msg) { + term.output("
" + JSON.stringify(JSON.parse(msg.data), null, 2)); + } + setTimeout(function() { + ws.send(" "); + }, 60000); + setTimeout(function() { + fetch("/api/status") + .then(function(response) { + return response.text(); + }).then(function(body) { + status.textContent = JSON.parse(body).clientsCount; + }) + }, 5000); +}) \ No newline at end of file diff --git a/juick-ws/src/main/static/style.css b/juick-ws/src/main/static/style.css new file mode 100644 index 00000000..ee5ec6b7 --- /dev/null +++ b/juick-ws/src/main/static/style.css @@ -0,0 +1,2 @@ +@import "normalize.css"; +@import "terminal"; \ No newline at end of file diff --git a/juick-ws/src/main/webapp/WEB-INF/templates/index.html b/juick-ws/src/main/webapp/WEB-INF/templates/index.html new file mode 100644 index 00000000..e888e365 --- /dev/null +++ b/juick-ws/src/main/webapp/WEB-INF/templates/index.html @@ -0,0 +1,14 @@ + + + + + Status + + + + +

+
+ + + \ No newline at end of file diff --git a/juick-ws/webpack.config.js b/juick-ws/webpack.config.js new file mode 100644 index 00000000..e6d33a06 --- /dev/null +++ b/juick-ws/webpack.config.js @@ -0,0 +1,29 @@ +var webpack = require("webpack"); +var globby = require("globby"); +var precss = require('precss'); +var autoprefixer = require('autoprefixer'); +var ExtractTextPlugin = require("extract-text-webpack-plugin") +module.exports = { + entry: { + "scripts" : globby.sync([ + __dirname + "/src/main/static/*.*" + ]) + }, + output: { + path: __dirname + "/src/main/webapp", + filename: "[name].js" + }, + module: { + loaders: [ + { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader") } + ] + }, + plugins: [ + new ExtractTextPlugin("style.css", { + allChunks: true + }) + ], + postcss: function() { + return [require("postcss-import")({ addDependencyTo: webpack, path: ['node_modules'] }), precss({'import': {disable: true}}), autoprefixer]; + } +} \ No newline at end of file -- cgit v1.2.3