| @@ -0,0 +1,10 @@ | |||||
| .DS_Store | |||||
| .idea/ | |||||
| *.iml | |||||
| target/ | |||||
| 2017* | |||||
| .project | |||||
| .classpath | |||||
| .settings/ | |||||
| *.log | |||||
| bin/ | |||||
| @@ -0,0 +1,257 @@ | |||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |||||
| <modelVersion>4.0.0</modelVersion> | |||||
| <groupId>com.educoder</groupId> | |||||
| <artifactId>webssh</artifactId> | |||||
| <packaging>war</packaging> | |||||
| <version>1.0-SNAPSHOT</version> | |||||
| <name>webssh Maven Webapp</name> | |||||
| <url>http://maven.apache.org</url> | |||||
| <properties> | |||||
| <swagger2.version>2.6.1</swagger2.version> | |||||
| <spring.version>4.3.6.RELEASE</spring.version> | |||||
| <freemarker.version>2.3.25-incubating</freemarker.version> | |||||
| <jsch.version>0.1.54</jsch.version> | |||||
| <javax.version>7.0</javax.version> | |||||
| <commons-lang.version>2.6</commons-lang.version> | |||||
| <commons-io.version>2.4</commons-io.version> | |||||
| <slf4j.version>1.7.21</slf4j.version> | |||||
| <fastjson.version>1.2.20</fastjson.version> | |||||
| <jackson.version>2.8.6</jackson.version> | |||||
| <codec.version>1.10</codec.version> | |||||
| <maven.compiler.source>1.8</maven.compiler.source> | |||||
| <maven.compiler.target>1.8</maven.compiler.target> | |||||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||||
| </properties> | |||||
| <dependencies> | |||||
| <dependency> | |||||
| <groupId>org.freemarker</groupId> | |||||
| <artifactId>freemarker</artifactId> | |||||
| <version>${freemarker.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.jcraft</groupId> | |||||
| <artifactId>jsch</artifactId> | |||||
| <version>${jsch.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>io.springfox</groupId> | |||||
| <artifactId>springfox-swagger2</artifactId> | |||||
| <version>${swagger2.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>io.springfox</groupId> | |||||
| <artifactId>springfox-swagger-ui</artifactId> | |||||
| <version>${swagger2.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.springframework</groupId> | |||||
| <artifactId>spring-webmvc</artifactId> | |||||
| <version>${spring.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.springframework</groupId> | |||||
| <artifactId>spring-websocket</artifactId> | |||||
| <version>${spring.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.springframework</groupId> | |||||
| <artifactId>spring-context-support</artifactId> | |||||
| <version>${spring.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.springframework</groupId> | |||||
| <artifactId>spring-test</artifactId> | |||||
| <version>${spring.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>javax</groupId> | |||||
| <artifactId>javaee-api</artifactId> | |||||
| <version>${javax.version}</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-lang</groupId> | |||||
| <artifactId>commons-lang</artifactId> | |||||
| <version>${commons-lang.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-io</groupId> | |||||
| <artifactId>commons-io</artifactId> | |||||
| <version>${commons-io.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>junit</groupId> | |||||
| <artifactId>junit</artifactId> | |||||
| <version>4.12</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.springframework</groupId> | |||||
| <artifactId>spring-aop</artifactId> | |||||
| <version>4.3.6.RELEASE</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.aspectj</groupId> | |||||
| <artifactId>aspectjrt</artifactId> | |||||
| <version>1.8.10</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.springframework</groupId> | |||||
| <artifactId>spring-aspects</artifactId> | |||||
| <version>${spring.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.springframework</groupId> | |||||
| <artifactId>spring-test</artifactId> | |||||
| <version>${spring.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>ch.qos.logback</groupId> | |||||
| <artifactId>logback-classic</artifactId> | |||||
| <version>1.2.3</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.logback-extensions</groupId> | |||||
| <artifactId>logback-ext-spring</artifactId> | |||||
| <version>0.1.4</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.alibaba</groupId> | |||||
| <artifactId>fastjson</artifactId> | |||||
| <version>${fastjson.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-codec</groupId> | |||||
| <artifactId>commons-codec</artifactId> | |||||
| <version>${codec.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.fasterxml.jackson.core</groupId> | |||||
| <artifactId>jackson-core</artifactId> | |||||
| <version>${jackson.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.fasterxml.jackson.core</groupId> | |||||
| <artifactId>jackson-annotations</artifactId> | |||||
| <version>${jackson.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.fasterxml.jackson.core</groupId> | |||||
| <artifactId>jackson-databind</artifactId> | |||||
| <version>${jackson.version}</version> | |||||
| </dependency> | |||||
| </dependencies> | |||||
| <build> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-surefire-plugin</artifactId> | |||||
| <version>2.18.1</version> | |||||
| <configuration> | |||||
| <skipTests>true</skipTests> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-shade-plugin</artifactId> | |||||
| <version>3.0.0</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <phase>package</phase> | |||||
| <goals> | |||||
| <goal>shade</goal> | |||||
| </goals> | |||||
| <configuration> | |||||
| <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation> | |||||
| <transformers> | |||||
| <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> | |||||
| <resource>META-INF/spring.handlers</resource> | |||||
| </transformer> | |||||
| <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> | |||||
| <resource>META-INF/spring.schemas</resource> | |||||
| </transformer> | |||||
| </transformers> | |||||
| <filters> | |||||
| <filter> | |||||
| <artifact>*:*</artifact> | |||||
| <excludes> | |||||
| <exclude>META-INF/*.SF</exclude> | |||||
| <exclude>META-INF/*.DSA</exclude> | |||||
| <exclude>META-INF/*.RSA</exclude> | |||||
| </excludes> | |||||
| </filter> | |||||
| </filters> | |||||
| </configuration> | |||||
| </execution> | |||||
| </executions> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.tomcat.maven</groupId> | |||||
| <artifactId>tomcat7-maven-plugin</artifactId> | |||||
| <version>2.2</version> | |||||
| <configuration> | |||||
| <address>0.0.0.0</address> | |||||
| <port>9001</port> | |||||
| <path>/</path> | |||||
| <uriEncoding>UTF-8</uriEncoding> | |||||
| <finalName>webssh</finalName> | |||||
| <server>tomcat7</server> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-compiler-plugin</artifactId> | |||||
| <version>2.3.2</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <id>compile</id> | |||||
| <phase>compile</phase> | |||||
| <goals> | |||||
| <goal>compile</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| <execution> | |||||
| <id>testCompile</id> | |||||
| <phase>test-compile</phase> | |||||
| <goals> | |||||
| <goal>testCompile</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| <configuration> | |||||
| <source>1.8</source> | |||||
| <target>1.8</target> | |||||
| </configuration> | |||||
| </plugin> | |||||
| </plugins> | |||||
| <finalName>webssh</finalName> | |||||
| </build> | |||||
| </project> | |||||
| @@ -0,0 +1,25 @@ | |||||
| package com.educoder.bridge.controller; | |||||
| import org.springframework.web.bind.annotation.ModelAttribute; | |||||
| import javax.servlet.http.HttpServletRequest; | |||||
| import javax.servlet.http.HttpServletResponse; | |||||
| import javax.servlet.http.HttpSession; | |||||
| /** | |||||
| * @author lqk | |||||
| * @version 0.1 | |||||
| */ | |||||
| public class BaseController { | |||||
| protected HttpServletRequest request; | |||||
| protected HttpServletResponse response; | |||||
| protected HttpSession session; | |||||
| @ModelAttribute | |||||
| public void setReqAndRes(HttpServletRequest request, HttpServletResponse response) { | |||||
| this.request = request; | |||||
| this.response = response; | |||||
| this.session = request.getSession(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,41 @@ | |||||
| package com.educoder.bridge.controller; | |||||
| import io.swagger.annotations.Api; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import org.springframework.web.bind.annotation.RequestMapping; | |||||
| import org.springframework.web.bind.annotation.RequestMethod; | |||||
| import org.springframework.web.bind.annotation.RequestParam; | |||||
| import org.springframework.web.bind.annotation.RestController; | |||||
| import org.springframework.web.servlet.ModelAndView; | |||||
| /** | |||||
| * @author guange | |||||
| * | |||||
| * @date 2017/08/02 | |||||
| */ | |||||
| @Api(value = "提供webssh连接", hidden = true) | |||||
| @RestController | |||||
| public class MainController extends BaseController { | |||||
| private final static Logger logger = LoggerFactory.getLogger(MainController.class); | |||||
| @RequestMapping(value={"/", "ssh"}, method= RequestMethod.GET) | |||||
| public ModelAndView index(@RequestParam("host")String host, | |||||
| @RequestParam("port")int port, | |||||
| @RequestParam("username")String username, | |||||
| @RequestParam("password")String password, | |||||
| @RequestParam("rows")int rows) { | |||||
| logger.debug("/ssh: 接收到连接请求, host: {}, port: {}", host, port); | |||||
| ModelAndView mv = new ModelAndView(); | |||||
| mv.setViewName("index"); | |||||
| mv.addObject("host", host); | |||||
| mv.addObject("port", port); | |||||
| mv.addObject("username", username); | |||||
| mv.addObject("password", password); | |||||
| mv.addObject("rows", rows); | |||||
| mv.addObject("digest", System.currentTimeMillis()); | |||||
| return mv; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| package com.educoder.bridge.handler; | |||||
| import com.educoder.bridge.service.JchService; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.web.socket.CloseStatus; | |||||
| import org.springframework.web.socket.TextMessage; | |||||
| import org.springframework.web.socket.WebSocketSession; | |||||
| import org.springframework.web.socket.handler.TextWebSocketHandler; | |||||
| public class WebsshHandler extends TextWebSocketHandler { | |||||
| @Autowired | |||||
| JchService jchService; | |||||
| @Override | |||||
| public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { | |||||
| super.afterConnectionEstablished(wsSession); | |||||
| jchService.add(wsSession); | |||||
| } | |||||
| /** | |||||
| * 重写handleTextMessage方法,用于处理从websocket接收到的信息 | |||||
| */ | |||||
| @Override | |||||
| protected void handleTextMessage(WebSocketSession wsSession, TextMessage message) throws Exception { | |||||
| jchService.recv(message.getPayload(), wsSession); | |||||
| super.handleTextMessage(wsSession, message); | |||||
| } | |||||
| @Override | |||||
| public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status) throws Exception { | |||||
| super.afterConnectionClosed(wsSession, status); | |||||
| jchService.closeByWebSocket(wsSession); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,41 @@ | |||||
| package com.educoder.bridge.model; | |||||
| public class SSHInfo { | |||||
| private String host; | |||||
| private String port; | |||||
| private String username; | |||||
| private String password; | |||||
| public void setHost(String host) { | |||||
| this.host = host; | |||||
| } | |||||
| public void setPort(String port) { | |||||
| this.port = port; | |||||
| } | |||||
| public void setUsername(String username) { | |||||
| this.username = username; | |||||
| } | |||||
| public void setPassword(String password) { | |||||
| this.password = password; | |||||
| } | |||||
| public String getHost() { | |||||
| return host; | |||||
| } | |||||
| public int getPort() { | |||||
| return Integer.parseInt(port); | |||||
| } | |||||
| public String getUsername() { | |||||
| return username; | |||||
| } | |||||
| public String getPassword() { | |||||
| return password; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,45 @@ | |||||
| package com.educoder.bridge.model; | |||||
| import com.jcraft.jsch.ChannelShell; | |||||
| import org.springframework.web.socket.WebSocketSession; | |||||
| import java.io.OutputStream; | |||||
| public class SSHSession { | |||||
| private WebSocketSession webSocketSession; | |||||
| private OutputStream outputStream; | |||||
| private ChannelShell channel; | |||||
| private SSHInfo SSHInfo; | |||||
| public SSHInfo getSSHInfo() { | |||||
| return SSHInfo; | |||||
| } | |||||
| public void setSSHInfo(SSHInfo SSHInfo) { | |||||
| this.SSHInfo = SSHInfo; | |||||
| } | |||||
| public ChannelShell getChannel() { | |||||
| return channel; | |||||
| } | |||||
| public void setChannel(ChannelShell channel) { | |||||
| this.channel = channel; | |||||
| } | |||||
| public WebSocketSession getWebSocketSession() { | |||||
| return webSocketSession; | |||||
| } | |||||
| public void setWebSocketSession(WebSocketSession webSocketSession) { | |||||
| this.webSocketSession = webSocketSession; | |||||
| } | |||||
| public OutputStream getOutputStream() { | |||||
| return outputStream; | |||||
| } | |||||
| public void setOutputStream(OutputStream outputStream) { | |||||
| this.outputStream = outputStream; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,261 @@ | |||||
| package com.educoder.bridge.service; | |||||
| import com.alibaba.fastjson.JSONObject; | |||||
| import com.educoder.bridge.model.SSHInfo; | |||||
| import com.educoder.bridge.model.SSHSession; | |||||
| import com.educoder.bridge.utils.Base64Util; | |||||
| import com.jcraft.jsch.ChannelShell; | |||||
| import com.jcraft.jsch.JSch; | |||||
| import com.jcraft.jsch.Session; | |||||
| import com.jcraft.jsch.UserInfo; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import org.springframework.stereotype.Service; | |||||
| import org.springframework.web.socket.TextMessage; | |||||
| import org.springframework.web.socket.WebSocketSession; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.io.OutputStream; | |||||
| import java.util.Arrays; | |||||
| import java.util.List; | |||||
| import java.util.Optional; | |||||
| import java.util.concurrent.CopyOnWriteArrayList; | |||||
| import java.util.concurrent.ExecutorService; | |||||
| import java.util.concurrent.Executors; | |||||
| @Service | |||||
| public class JchService { | |||||
| private static List<SSHSession> sshSessionQueue = new CopyOnWriteArrayList<>(); | |||||
| private ExecutorService executorService = Executors.newCachedThreadPool(); | |||||
| private Logger logger = LoggerFactory.getLogger(getClass()); | |||||
| com.jcraft.jsch.Logger jschLogger = new com.jcraft.jsch.Logger() { | |||||
| @Override | |||||
| public boolean isEnabled(int arg0) { | |||||
| return true; | |||||
| } | |||||
| @Override | |||||
| public void log(int arg0, String arg1) { | |||||
| if (logger.isTraceEnabled()) { | |||||
| logger.trace("JSch Log [Level " + arg0 + "]: " + arg1); | |||||
| } | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * 在webSocket连接时,初始化一个ssh连接 | |||||
| * | |||||
| * @param webSocketSession webSocket连接 | |||||
| */ | |||||
| public void add(WebSocketSession webSocketSession) { | |||||
| SSHSession sshSession = new SSHSession(); | |||||
| sshSession.setWebSocketSession(webSocketSession); | |||||
| sshSessionQueue.add(sshSession); | |||||
| } | |||||
| /** | |||||
| * 处理客户端发过来的数据 | |||||
| * @param buffer 数据 | |||||
| * @param webSocketSession webSocket连接 | |||||
| */ | |||||
| public void recv(String buffer, WebSocketSession webSocketSession) { | |||||
| SSHSession sshSession = null; | |||||
| try { | |||||
| logger.debug("webSocketSessionID: {}, 信息: {}", webSocketSession.getId(), buffer); | |||||
| JSONObject info = JSONObject.parseObject(buffer); | |||||
| String tp = info.getString("tp"); | |||||
| sshSession = findByWebSocketSession(webSocketSession); | |||||
| //初始化连接 | |||||
| if ("init".equals(tp)) { | |||||
| // {"tp":"init","data":{"host":"127.0.0.1","port":"41080","username":"root","password":"123123"}} | |||||
| SSHInfo sshInfo = info.getObject("data", SSHInfo.class); | |||||
| sshSession.setSSHInfo(sshInfo); | |||||
| if (sshSession != null) { | |||||
| SSHSession finalSSHSession = sshSession; | |||||
| // 新开一个线程建立连接,连接开启之后以一直监听来自客户端的输入 | |||||
| executorService.execute(() -> { | |||||
| connectTossh(finalSSHSession); | |||||
| }); | |||||
| } | |||||
| } else if ("client".equals(tp)) { | |||||
| String data = info.getString("data"); | |||||
| // 将网页输入的数据传送给后端服务器 | |||||
| if (sshSession != null) { | |||||
| transTossh(sshSession.getOutputStream(), data); | |||||
| } | |||||
| } | |||||
| } catch (Exception e) { | |||||
| logger.error("转发命令到ssh出错: {}", e); | |||||
| close(sshSession); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 将数据传送给服务端作为SSH的输入 | |||||
| * | |||||
| * @param outputStream | |||||
| * @param data | |||||
| * @throws IOException | |||||
| */ | |||||
| private void transTossh(OutputStream outputStream, String data) throws IOException { | |||||
| if (outputStream != null) { | |||||
| outputStream.write(data.getBytes()); | |||||
| outputStream.flush(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 连接ssh | |||||
| * | |||||
| * @param sshSession ssh连接需要的信息 | |||||
| */ | |||||
| private void connectTossh(SSHSession sshSession){ | |||||
| Session jschSession = null; | |||||
| SSHInfo SSHInfo = sshSession.getSSHInfo(); | |||||
| try { | |||||
| JSch jsch = new JSch(); | |||||
| JSch.setLogger(jschLogger); | |||||
| //启动线程 | |||||
| java.util.Properties config = new java.util.Properties(); | |||||
| config.put("StrictHostKeyChecking", "no"); | |||||
| jschSession = jsch.getSession(SSHInfo.getUsername(), SSHInfo.getHost(), SSHInfo.getPort()); | |||||
| jschSession.setConfig(config); | |||||
| jschSession.setPassword(SSHInfo.getPassword()); | |||||
| jschSession.setUserInfo(new UserInfo() { | |||||
| @Override | |||||
| public String getPassphrase() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public String getPassword() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public boolean promptPassword(String s) { | |||||
| return false; | |||||
| } | |||||
| @Override | |||||
| public boolean promptPassphrase(String s) { | |||||
| return false; | |||||
| } | |||||
| @Override | |||||
| public boolean promptYesNo(String s) { | |||||
| return true; | |||||
| } // Accept all server keys | |||||
| @Override | |||||
| public void showMessage(String s) { | |||||
| } | |||||
| }); | |||||
| jschSession.connect(); | |||||
| ChannelShell channel = (ChannelShell) jschSession.openChannel("shell"); | |||||
| channel.setPtyType("xterm"); | |||||
| channel.connect(); | |||||
| sshSession.setChannel(channel); | |||||
| InputStream inputStream = channel.getInputStream(); | |||||
| sshSession.setOutputStream(channel.getOutputStream()); | |||||
| sshSession.setSSHInfo(SSHInfo); | |||||
| logger.debug("主机: {} 连接成功!", SSHInfo.getHost()); | |||||
| // 循环读取,jsch的输入为服务器执行命令之后的返回数据 | |||||
| byte[] buf = new byte[1024]; | |||||
| while (true) { | |||||
| int length = inputStream.read(buf); | |||||
| if (length < 0) { | |||||
| close(sshSession); | |||||
| throw new Exception("读取出错,数据长度:" + length); | |||||
| } | |||||
| sendMsg(sshSession.getWebSocketSession(), Arrays.copyOfRange(buf, 0, length)); | |||||
| } | |||||
| } catch (Exception e) { | |||||
| logger.error("ssh连接出错, e: {}", e); | |||||
| } finally { | |||||
| logger.info("连接关闭, {}", SSHInfo.getHost()); | |||||
| if (jschSession != null) { | |||||
| jschSession.disconnect(); | |||||
| } | |||||
| close(sshSession); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 发送数据回websocket | |||||
| * | |||||
| * @param webSocketSession webSocket连接 | |||||
| * @param buffer 数据 | |||||
| * @throws IOException | |||||
| */ | |||||
| public void sendMsg(WebSocketSession webSocketSession, byte[] buffer) throws IOException { | |||||
| logger.debug("服务端返回的数据: {}", new String(buffer, "UTF-8")); | |||||
| webSocketSession.sendMessage(new TextMessage(Base64Util.encodeBytes(buffer))); | |||||
| } | |||||
| /** | |||||
| * 通过webSocket连接在队列中找到对应的SSH连接 | |||||
| * | |||||
| * @param webSocketSession webSocket连接 | |||||
| */ | |||||
| public SSHSession findByWebSocketSession(WebSocketSession webSocketSession) { | |||||
| Optional<SSHSession> optional = sshSessionQueue.stream().filter(webscoketObj -> webscoketObj.getWebSocketSession() == webSocketSession).findFirst(); | |||||
| if (optional.isPresent()) { | |||||
| return optional.get(); | |||||
| } | |||||
| return null; | |||||
| } | |||||
| /** | |||||
| * 关闭ssh和websocket连接 | |||||
| * | |||||
| * @param sshSession ssh连接 | |||||
| */ | |||||
| private void close(SSHSession sshSession) { | |||||
| if (sshSession != null) { | |||||
| sshSession.getChannel().disconnect(); | |||||
| try { | |||||
| sshSession.getWebSocketSession().close(); | |||||
| sshSession.getOutputStream().close(); | |||||
| } catch (IOException e) { | |||||
| logger.error("连接关闭失败!e: {}", e); | |||||
| } | |||||
| sshSessionQueue.remove(sshSession); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 通过webSocketSession关闭ssh与webSocket连接 | |||||
| * | |||||
| * @param webSocketSession | |||||
| */ | |||||
| public void closeByWebSocket(WebSocketSession webSocketSession) { | |||||
| close(findByWebSocketSession(webSocketSession)); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,52 @@ | |||||
| package com.educoder.bridge.utils; | |||||
| import org.apache.commons.codec.binary.Base64; | |||||
| import java.nio.charset.StandardCharsets; | |||||
| /** | |||||
| * Created by guange on 23/02/2017. | |||||
| */ | |||||
| public class Base64Util { | |||||
| /** | |||||
| * base64编码 | |||||
| * | |||||
| * @param code | |||||
| * @return | |||||
| */ | |||||
| public static String encode(String code) { | |||||
| byte[] encode = Base64.encodeBase64URLSafe(code.getBytes(StandardCharsets.UTF_8)); | |||||
| return new String(encode, StandardCharsets.UTF_8); | |||||
| } | |||||
| public static byte[] encodeBytes(byte[] codes) { | |||||
| return Base64.encodeBase64(codes); | |||||
| } | |||||
| /** | |||||
| * base64解码 | |||||
| * | |||||
| * @param code | |||||
| * @return | |||||
| */ | |||||
| public static String decode(String code) { | |||||
| byte[] decode = Base64.decodeBase64(code); | |||||
| return new String(decode, StandardCharsets.UTF_8); | |||||
| } | |||||
| /** | |||||
| * base64再解码,把原本的非URL safe编码转换为URL safe编码 | |||||
| * | |||||
| * @param code | |||||
| * @return | |||||
| */ | |||||
| public static String reencode(String code) { | |||||
| String str = decode(code); | |||||
| str = str.replace("\n", "\r\n"); | |||||
| return encode(str); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,42 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <beans xmlns="http://www.springframework.org/schema/beans" | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| xmlns:context="http://www.springframework.org/schema/context" | |||||
| xmlns:websocket="http://www.springframework.org/schema/websocket" | |||||
| xmlns:aop="http://www.springframework.org/schema/aop" | |||||
| xsi:schemaLocation="http://www.springframework.org/schema/beans | |||||
| http://www.springframework.org/schema/beans/spring-beans.xsd | |||||
| http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd | |||||
| http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd | |||||
| http://www.springframework.org/schema/websocket | |||||
| http://www.springframework.org/schema/websocket/spring-websocket.xsd"> | |||||
| <aop:aspectj-autoproxy/> | |||||
| <context:component-scan base-package="com.educoder.bridge.controller"/> | |||||
| <context:component-scan base-package="com.educoder.bridge.service"/> | |||||
| <context:component-scan base-package="com.educoder.bridge.handler"/> | |||||
| <!-- freemaker配置 --> | |||||
| <bean id="freemarkerConfig" | |||||
| class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> | |||||
| <property name="templateLoaderPath" value="/WEB-INF/pages/" /> | |||||
| <property name="freemarkerSettings"> | |||||
| <props> | |||||
| <prop key="template_update_delay">0</prop> | |||||
| <prop key="default_encoding">UTF-8</prop> | |||||
| <prop key="number_format">0.##########</prop> | |||||
| <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop> | |||||
| <prop key="classic_compatible">true</prop> | |||||
| <prop key="template_exception_handler">ignore</prop> | |||||
| </props> | |||||
| </property> | |||||
| </bean> | |||||
| <!--注册消息处理器,指定WebsshHandler处理消息,并将/ws映射到其中--> | |||||
| <websocket:handlers allowed-origins="*"> | |||||
| <websocket:mapping path="/ws" handler="websshHandler"/> | |||||
| </websocket:handlers> | |||||
| <bean id="websshHandler" class="com.educoder.bridge.handler.WebsshHandler"/> | |||||
| </beans> | |||||
| @@ -0,0 +1,47 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <configuration> | |||||
| <property name="log_path" value="/home/ww/test/tomcat-test/logs/"/> | |||||
| <!-- 打印在标准控制台 --> | |||||
| <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | |||||
| <encoder> | |||||
| <!--格式化输出:%d:时间,%thread:线程名,%-5level:级别从左显示5个字符宽度, | |||||
| %logger{50}:输出日志的类, 50代表包名加类名的总长度限制, %M 方法名 %L 行号 %msg:日志消息,%n是换行符--> | |||||
| <pattern>%d{MM-dd HH:mm:ss} [%thread] %-5level %logger{30} %M %L - %msg%n</pattern> | |||||
| </encoder> | |||||
| <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> | |||||
| <level>DEBUG</level> | |||||
| </filter> | |||||
| </appender> | |||||
| <!-- 错误信息 --> | |||||
| <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"> | |||||
| <Encoding>UTF-8</Encoding> | |||||
| <encoder> | |||||
| <pattern>%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{30} %M - %msg%n%L</pattern> | |||||
| </encoder> | |||||
| <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> | |||||
| <level>ERROR</level> | |||||
| </filter> | |||||
| <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> | |||||
| <fileNamePattern>${log_path}error.%d{MM-dd}.log</fileNamePattern> | |||||
| </rollingPolicy> | |||||
| </appender> | |||||
| <!-- 屏蔽框架输出 --> | |||||
| <logger name="org.slf4j" level="ERROR"/> | |||||
| <logger name="org.springframework" level="ERROR"/> | |||||
| <logger name="io.swagger" level="ERROR"/> | |||||
| <logger name="ch.qos.logback" level="OFF"/> | |||||
| <logger name="springfox.documentation" level="ERROR"/> | |||||
| <!-- 所有的日志同时应用“STDOUT”和“EROOR”的策略 --> | |||||
| <root> | |||||
| <level value="DEBUG"/> | |||||
| <!--<appender-ref ref="TPM"/>--> | |||||
| <appender-ref ref="ERROR"/> | |||||
| <appender-ref ref="STDOUT"/> | |||||
| </root> | |||||
| </configuration> | |||||
| @@ -0,0 +1,34 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <beans xmlns="http://www.springframework.org/schema/beans" | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| xmlns:context="http://www.springframework.org/schema/context" | |||||
| xmlns:mvc="http://www.springframework.org/schema/mvc" | |||||
| xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> | |||||
| <!--指明 controller 所在包,并扫描其中的注解--> | |||||
| <context:component-scan base-package="com.educoder.bridge.controller"/> | |||||
| <!-- 静态资源(js、image等)的访问 --> | |||||
| <mvc:default-servlet-handler/> | |||||
| <!--ViewResolver 视图解析器--> | |||||
| <!--用于支持freemarker视图解析--> | |||||
| <!--视图解释器 --> | |||||
| <bean id="viewResolver" | |||||
| class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> | |||||
| <property name="suffix"> | |||||
| <value>.ftl</value> | |||||
| </property> | |||||
| <property name="contentType" value="text/html;charset=UTF-8"></property> | |||||
| </bean> | |||||
| <!-- 开启注解 --> | |||||
| <mvc:annotation-driven/> | |||||
| <bean class="springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration" id="swagger2Config"/> | |||||
| <mvc:resources location="classpath:/META-INF/resources/" mapping="swagger-ui.html"/> | |||||
| <mvc:resources location="classpath:/META-INF/resources/webjars/" mapping="/webjars/**"/> | |||||
| </beans> | |||||
| @@ -0,0 +1,50 @@ | |||||
| <!DOCTYPE html> | |||||
| <html> | |||||
| <head lang="en"> | |||||
| <meta charset="UTF-8"> | |||||
| <title>webssh</title> | |||||
| <link rel='shortcut icon' type='image/x-icon' href='/static/image/favicon.ico'/> | |||||
| <link rel="stylesheet" href="/static/css/pure-min.css"> | |||||
| <link href="/static/css/xterm.css" rel="stylesheet" type="text/css"/> | |||||
| </head> | |||||
| <div id="main" align="center" style="display: none;"> | |||||
| <form id="form" name="form" class="pure-form pure-form-stacked"> | |||||
| <fieldset> | |||||
| <input id="terminalRow" name="terminalRow" type="text" value=${rows}> | |||||
| <div class="pure-item"> | |||||
| <label for="host">Host</label> | |||||
| <input id="host" name="host" type="text" placeholder="Host" value=${host}> | |||||
| </div> | |||||
| <div class="pure-item"> | |||||
| <label for="port">Port</label> | |||||
| <input id="port" name="port" type="text" placeholder="Port" value=${port}> | |||||
| </div> | |||||
| <div class="pure-item"> | |||||
| <label for="username">Username</label> | |||||
| <input id="username" name="username" type="text" placeholder="Username" value=${username}> | |||||
| </div> | |||||
| <div class="pure-item"> | |||||
| <label for="password">Password</label> | |||||
| <input id="password" name="password" type="password" placeholder="Password" value=${password}> | |||||
| </div> | |||||
| <label for="remember" class="pure-checkbox"> | |||||
| <input id="remember" type="checkbox"> Remember me | |||||
| </label> | |||||
| <button type="button" class="pure-button pure-button-primary" onclick="connect()">Connect</button> | |||||
| </fieldset> | |||||
| </form> | |||||
| </div> | |||||
| <div id="term" align="center"></div> | |||||
| <script src="/static/js/base64.js"></script> | |||||
| <script src="/static/js/jquerymin.js"></script> | |||||
| <script src="/static/js/xterm.js"></script> | |||||
| <script src="/static/js/ws.js"></script> | |||||
| <script src="/static/js/formvalid.js"></script> | |||||
| <script src="/static/js/main.js?${digest}"></script> | |||||
| <script type="application/javascript"> | |||||
| $(function () { | |||||
| connect(); | |||||
| }) | |||||
| </script> | |||||
| </html> | |||||
| @@ -0,0 +1,63 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" | |||||
| version="3.1"> | |||||
| <display-name>educoder bridge</display-name> | |||||
| <!-- Spring 上下文参数 --> | |||||
| <context-param> | |||||
| <param-name>contextConfigLocation</param-name> | |||||
| <param-value>classpath:applicationContext.xml</param-value> | |||||
| </context-param> | |||||
| <listener> | |||||
| <listener-class> | |||||
| org.springframework.web.context.ContextLoaderListener | |||||
| </listener-class> | |||||
| </listener> | |||||
| <!-- logback --> | |||||
| <context-param> | |||||
| <param-name>logbackConfigLocation</param-name> | |||||
| <param-value>classpath:logback.xml</param-value> | |||||
| </context-param> | |||||
| <listener> | |||||
| <listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class> | |||||
| </listener> | |||||
| <servlet> | |||||
| <servlet-name>mvc-dispatcher</servlet-name> | |||||
| <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> | |||||
| <load-on-startup>1</load-on-startup> | |||||
| </servlet> | |||||
| <servlet-mapping> | |||||
| <servlet-name>mvc-dispatcher</servlet-name> | |||||
| <url-pattern>/</url-pattern> | |||||
| </servlet-mapping> | |||||
| <filter> | |||||
| <filter-name>encodingFilter</filter-name> | |||||
| <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> | |||||
| <init-param> | |||||
| <param-name>encoding</param-name> | |||||
| <param-value>UTF-8</param-value> | |||||
| </init-param> | |||||
| <init-param> | |||||
| <param-name>forceEncoding</param-name> | |||||
| <param-value>true</param-value> | |||||
| </init-param> | |||||
| </filter> | |||||
| <filter-mapping> | |||||
| <filter-name>encodingFilter</filter-name> | |||||
| <url-pattern>/*</url-pattern> | |||||
| </filter-mapping> | |||||
| <welcome-file-list> | |||||
| <welcome-file></welcome-file> | |||||
| </welcome-file-list> | |||||
| </web-app> | |||||
| @@ -0,0 +1,2 @@ | |||||
| <!--保留以防后续看swagger UI界面需要--> | |||||
| <!--<meta http-equiv="refresh" content="0; url=/swagger-ui.html" />--> | |||||
| @@ -0,0 +1,53 @@ | |||||
| .aside { | |||||
| text-align: center; | |||||
| background: #1f8dd6; | |||||
| height: 100px; | |||||
| color: #fff; | |||||
| vertical-align: middle; | |||||
| line-height: 100px; | |||||
| font-size: 30px | |||||
| } | |||||
| #main { | |||||
| margin-top: 20px; | |||||
| } | |||||
| #ratio-group { | |||||
| float: right; | |||||
| } | |||||
| .pure-item { | |||||
| margin: 0 auto 10px; | |||||
| width: 300px; | |||||
| position: relative; | |||||
| } | |||||
| .pure-radio { | |||||
| margin-left: 10px; | |||||
| } | |||||
| .pure-item:after { | |||||
| content: ""; | |||||
| display: table; | |||||
| clear: both; | |||||
| } | |||||
| .pure-item label { | |||||
| float: left; | |||||
| line-height: 34px; | |||||
| } | |||||
| .pure-item input { | |||||
| float: right; | |||||
| } | |||||
| .terminal { | |||||
| float: none; | |||||
| border: #000 solid 5px; | |||||
| font-family: "Monaco", "DejaVu Sans Mono", "Liberation Mono", monospace; | |||||
| font-size: 11px; | |||||
| color: #f0f0f0; | |||||
| width: 600px; | |||||
| background: #000; | |||||
| box-shadow: rgba(0, 0, 0, 0.8) 2px 2px 20px; | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| .tooltip{ | |||||
| position: absolute; | |||||
| max-width: 300px; | |||||
| top: 3px; | |||||
| left: 105%; | |||||
| padding: 8px 10px; | |||||
| border-radius: 5px; | |||||
| color: #fff; | |||||
| background: #000000; | |||||
| box-shadow: 0 2px 2px 0 #7F7C7C; | |||||
| white-space: nowrap; | |||||
| } | |||||
| .tooltip:after{ | |||||
| content: ''; | |||||
| position: absolute; | |||||
| top: 35%; | |||||
| right:100%; | |||||
| margin-left: 10px; | |||||
| width: 0; | |||||
| height: 0; | |||||
| border: 5px solid transparent; | |||||
| border-right: 7px #000; | |||||
| border-left-width: 7px; | |||||
| } | |||||
| @@ -0,0 +1,103 @@ | |||||
| function Base64() { | |||||
| // private property | |||||
| _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; | |||||
| // public method for encoding | |||||
| this.encode = function (input) { | |||||
| var output = ""; | |||||
| var chr1, chr2, chr3, enc1, enc2, enc3, enc4; | |||||
| var i = 0; | |||||
| input = _utf8_encode(input); | |||||
| while (i < input.length) { | |||||
| chr1 = input.charCodeAt(i++); | |||||
| chr2 = input.charCodeAt(i++); | |||||
| chr3 = input.charCodeAt(i++); | |||||
| enc1 = chr1 >> 2; | |||||
| enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); | |||||
| enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); | |||||
| enc4 = chr3 & 63; | |||||
| if (isNaN(chr2)) { | |||||
| enc3 = enc4 = 64; | |||||
| } else if (isNaN(chr3)) { | |||||
| enc4 = 64; | |||||
| } | |||||
| output = output + | |||||
| _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + | |||||
| _keyStr.charAt(enc3) + _keyStr.charAt(enc4); | |||||
| } | |||||
| return output; | |||||
| } | |||||
| // public method for decoding | |||||
| this.decode = function (input) { | |||||
| var output = ""; | |||||
| var chr1, chr2, chr3; | |||||
| var enc1, enc2, enc3, enc4; | |||||
| var i = 0; | |||||
| input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); | |||||
| while (i < input.length) { | |||||
| enc1 = _keyStr.indexOf(input.charAt(i++)); | |||||
| enc2 = _keyStr.indexOf(input.charAt(i++)); | |||||
| enc3 = _keyStr.indexOf(input.charAt(i++)); | |||||
| enc4 = _keyStr.indexOf(input.charAt(i++)); | |||||
| chr1 = (enc1 << 2) | (enc2 >> 4); | |||||
| chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); | |||||
| chr3 = ((enc3 & 3) << 6) | enc4; | |||||
| output = output + String.fromCharCode(chr1); | |||||
| if (enc3 != 64) { | |||||
| output = output + String.fromCharCode(chr2); | |||||
| } | |||||
| if (enc4 != 64) { | |||||
| output = output + String.fromCharCode(chr3); | |||||
| } | |||||
| } | |||||
| output = _utf8_decode(output); | |||||
| return output; | |||||
| } | |||||
| // private method for UTF-8 encoding | |||||
| _utf8_encode = function (string) { | |||||
| string = string.replace(/\r\n/g,"\n"); | |||||
| var utftext = ""; | |||||
| for (var n = 0; n < string.length; n++) { | |||||
| var c = string.charCodeAt(n); | |||||
| if (c < 128) { | |||||
| utftext += String.fromCharCode(c); | |||||
| } else if((c > 127) && (c < 2048)) { | |||||
| utftext += String.fromCharCode((c >> 6) | 192); | |||||
| utftext += String.fromCharCode((c & 63) | 128); | |||||
| } else { | |||||
| utftext += String.fromCharCode((c >> 12) | 224); | |||||
| utftext += String.fromCharCode(((c >> 6) & 63) | 128); | |||||
| utftext += String.fromCharCode((c & 63) | 128); | |||||
| } | |||||
| } | |||||
| return utftext; | |||||
| } | |||||
| // private method for UTF-8 decoding | |||||
| _utf8_decode = function (utftext) { | |||||
| var string = ""; | |||||
| var i = 0; | |||||
| var c = c1 = c2 = 0; | |||||
| while ( i < utftext.length ) { | |||||
| c = utftext.charCodeAt(i); | |||||
| if (c < 128) { | |||||
| string += String.fromCharCode(c); | |||||
| i++; | |||||
| } else if((c > 191) && (c < 224)) { | |||||
| c2 = utftext.charCodeAt(i+1); | |||||
| string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); | |||||
| i += 2; | |||||
| } else { | |||||
| c2 = utftext.charCodeAt(i+1); | |||||
| c3 = utftext.charCodeAt(i+2); | |||||
| string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); | |||||
| i += 3; | |||||
| } | |||||
| } | |||||
| return string; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,234 @@ | |||||
| /* | |||||
| Jquery | |||||
| janchie 2010.1 | |||||
| 1.02版 | |||||
| */ | |||||
| var validResult = {}; | |||||
| var errorMsg = {}; | |||||
| (function ($) { | |||||
| $.fn.extend({ | |||||
| valid: function () { | |||||
| if (!$(this).is("form")) return; | |||||
| var items = $.isArray(arguments[0]) ? arguments[0] : [], | |||||
| isBindSubmit = typeof arguments[1] === "boolean" ? arguments[1] : true, | |||||
| isAlert = typeof arguments[2] === "boolean" ? arguments[2] : false, | |||||
| rule = { | |||||
| "eng": /^[A-Za-z]+$/, | |||||
| "chn": /^[\u0391-\uFFE5]+$/, | |||||
| "mail": /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/, | |||||
| "url": /^http[s]?:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"\"])*$/, | |||||
| "currency": /^\d+(\.\d+)?$/, | |||||
| "number": /^\d+$/, | |||||
| "int": /^[0-9]{1,30}$/, | |||||
| "double": /^[-\+]?\d+(\.\d+)?$/, | |||||
| "username": /^[a-zA-Z]{1}([a-zA-Z0-9]|[._]){3,19}$/, | |||||
| "password": /^[\w\W]{6,20}$/, | |||||
| "safe": />|<|,|\[|\]|\{|\}|\?|\/|\+|=|\||\'|\\|\"|:|;|\~|\!|\@|\#|\*|\$|\%|\^|\&|\(|\)|`/i, | |||||
| "dbc": /[a-zA-Z0-9!@#¥%^&*()_+{}[]|:"';.,/?<>`~ ]/, | |||||
| "qq": /[1-9][0-9]{4,}/, | |||||
| "date": /^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$/, | |||||
| "year": /^(19|20)[0-9]{2}$/, | |||||
| "month": /^(0?[1-9]|1[0-2])$/, | |||||
| "day": /^((0?[1-9])|((1|2)[0-9])|30|31)$/, | |||||
| "hour": /^((0?[1-9])|((1|2)[0-3]))$/, | |||||
| "minute": /^((0?[1-9])|((1|5)[0-9]))$/, | |||||
| "second": /^((0?[1-9])|((1|5)[0-9]))$/, | |||||
| "mobile": /^((\(\d{2,3}\))|(\d{3}\-))?13\d{9}$/, | |||||
| "phone": /^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/, | |||||
| "zipcode": /^[1-9]\d{5}$/, | |||||
| "IDcard": /^((1[1-5])|(2[1-3])|(3[1-7])|(4[1-6])|(5[0-4])|(6[1-5])|71|(8[12])|91)\d{4}((19\d{2}(0[13-9]|1[012])(0[1-9]|[12]\d|30))|(19\d{2}(0[13578]|1[02])31)|(19\d{2}02(0[1-9]|1\d|2[0-8]))|(19([13579][26]|[2468][048]|0[48])0229))\d{3}(\d|X|x)?$/, | |||||
| "ip": /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/, | |||||
| "file": /^[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/, | |||||
| "image": /.+\.(jpg|gif|png|bmp)$/i, | |||||
| "word": /.+\.(doc|rtf|pdf)$/i, | |||||
| "port": function (port) { | |||||
| return (!isNaN(port) && port > 0 && port < 65536) ? true : false; | |||||
| }, | |||||
| "eq": function (arg1, arg2) { | |||||
| return arg1 == arg2 ? true : false; | |||||
| }, | |||||
| "gt": function (arg1, arg2) { | |||||
| return arg1 > arg2 ? true : false; | |||||
| }, | |||||
| "gte": function (arg1, arg2) { | |||||
| return arg1 >= arg2 ? true : false; | |||||
| }, | |||||
| "lt": function (arg1, arg2) { | |||||
| return arg1 < arg2 ? true : false; | |||||
| }, | |||||
| "lte": function (arg1, arg2) { | |||||
| return arg1 <= arg2 ? true : false; | |||||
| } | |||||
| }, | |||||
| msgSuffix = { | |||||
| "eng": "only english welcomed", | |||||
| "chn": "only chinese welcomed", | |||||
| "mail": "invalid email format", | |||||
| "url": "invalid url format", | |||||
| "currency": "invalid number format", | |||||
| "number": "only number welcomed", | |||||
| "int": "only integer welcomed", | |||||
| "double": "only float welcomed", | |||||
| "username": "invalid username format,4-20 characters", | |||||
| "password": "warning, you'd better use 6-20 characters", | |||||
| "safe": "forbidden special characters", | |||||
| "dbc": "forbidden full width characters", | |||||
| "qq": "invalid qq format", | |||||
| "date": "invalid date format", | |||||
| "year": "invalid year format", | |||||
| "month": "invalid month format", | |||||
| "day": "invalid day format", | |||||
| "hour": "invalid hour format", | |||||
| "minute": "invalid minute format", | |||||
| "second": "invalid second format", | |||||
| "mobile": "invalid mobile format", | |||||
| "phone": "invalid phone format", | |||||
| "zipcode": "invalid zipcode format", | |||||
| "IDcard": "invalid identity format", | |||||
| "ip": "invalid ip format", | |||||
| "port": "invalid port format", | |||||
| "file": "invalid file format", | |||||
| "image": "invalid image format", | |||||
| "word": "invalid word file format", | |||||
| "eq": "not equal", | |||||
| "gt": "no greater than", | |||||
| "gte": "no greater than or equal", | |||||
| "lt": "no smaller than", | |||||
| "lte": "no smaller than or equal" | |||||
| }, | |||||
| msg = "", formObj = $(this), checkRet = true, isAll, | |||||
| tipname = function (namestr) { | |||||
| return "tip_" + namestr.replace(/([a-zA-Z0-9])/g, "-$1"); | |||||
| }, | |||||
| typeTest = function () { | |||||
| var result = true, args = arguments; | |||||
| if (rule.hasOwnProperty(args[0])) { | |||||
| var t = rule[args[0]], v = args[1]; | |||||
| result = args.length > 2 ? t.apply(arguments, [].slice.call(args, 1)) : ($.isFunction(t) ? t(v) : t.test(v)); | |||||
| } | |||||
| return result; | |||||
| }, | |||||
| showError = function (fieldObj, filedName, warnInfo) { | |||||
| checkRet = false; | |||||
| var tipObj = $("#" + tipname(filedName)); | |||||
| if (tipObj.length > 0) tipObj.remove(); | |||||
| var tipPosition = fieldObj.next().length > 0 ? fieldObj.nextAll().eq(this.length - 1) : fieldObj.eq(this.length - 1); | |||||
| //tipPosition.after("<span class='tooltip' id='" + tipname(filedName) + "'> " + warnInfo + " </span>"); | |||||
| validResult[filedName] = false; | |||||
| errorMsg[filedName] = warnInfo; | |||||
| if (isAlert && isAll) msg = warnInfo; | |||||
| }, | |||||
| showRight = function (fieldObj, filedName) { | |||||
| var tipObj = $("#" + tipname(filedName)); | |||||
| if (tipObj.length > 0) tipObj.remove(); | |||||
| var tipPosition = fieldObj.next().length > 0 ? fieldObj.nextAll().eq(this.length - 1) : fieldObj.eq(this.length - 1); | |||||
| //tipPosition.after("<span class='tooltip' id='" + tipname(filedName) + "'>correct</span>"); | |||||
| validResult[filedName] = true; | |||||
| }, | |||||
| findTo = function (objName) { | |||||
| var find; | |||||
| $.each(items, function () { | |||||
| if (this.name == objName && this.simple) { | |||||
| find = this.simple; | |||||
| return false; | |||||
| } | |||||
| }); | |||||
| if (!find) find = $("[name='" + objName + "']")[0].name; | |||||
| return find; | |||||
| }, | |||||
| fieldCheck = function (item) { | |||||
| var i = item, field = $("[name='" + i.name + "']", formObj[0]); | |||||
| if (!field[0]) return; | |||||
| var warnMsg, fv = $.trim(field.val()), isRq = typeof i.require === "boolean" ? i.require : true; | |||||
| if (isRq && ((field.is(":radio") || field.is(":checkbox")) && !field.is(":checked"))) { | |||||
| warnMsg = i.message || "choice needed"; | |||||
| showError(field, i.name, warnMsg); | |||||
| } else if (isRq && fv == "") { | |||||
| warnMsg = i.message || ( field.is("select") ? "choice needed" : "not none" ); | |||||
| showError(field, i.name, warnMsg); | |||||
| } else if (fv != "") { | |||||
| if (i.min || i.max) { | |||||
| var len = fv.length, min = i.min || 0, max = i.max; | |||||
| warnMsg = i.message || (max ? "range" + min + "~" + max + "" : "min length" + min); | |||||
| if ((max && (len > max || len < min)) || (!max && len < min)) { | |||||
| showError(field, i.name, warnMsg); | |||||
| return; | |||||
| } | |||||
| } | |||||
| if (i.type) { | |||||
| var matchVal = i.to ? $.trim($("[name='" + i.to + "']").val()) : i.value; | |||||
| var matchRet = matchVal ? typeTest(i.type, fv, matchVal) : typeTest(i.type, fv); | |||||
| warnMsg = i.message || msgSuffix[i.type]; | |||||
| if (matchVal) warnMsg += (i.to ? findTo(i.to) + "value" : i.value); | |||||
| if (!matchRet) showError(field, i.name, warnMsg); | |||||
| else showRight(field, i.name); | |||||
| } else { | |||||
| showRight(field, i.name); | |||||
| } | |||||
| } else if (isRq) { | |||||
| showRight(field, i.name); | |||||
| } | |||||
| }, | |||||
| validate = function () { | |||||
| $.each(items, function () { | |||||
| isAll = true; | |||||
| fieldCheck(this); | |||||
| }); | |||||
| if (isAlert && msg != "") { | |||||
| alert(msg); | |||||
| msg = ""; | |||||
| } | |||||
| return checkRet; | |||||
| }; | |||||
| $.each(items, function () { | |||||
| var field = $("[name='" + this.name + "']", formObj[0]); | |||||
| if (field.is(":hidden")) return; | |||||
| var obj = this, toCheck = function () { | |||||
| isAll = false; | |||||
| fieldCheck(obj); | |||||
| }; | |||||
| if (field.is(":file") || field.is("select")) { | |||||
| field.change(toCheck); | |||||
| } else { | |||||
| field.blur(toCheck); | |||||
| } | |||||
| }); | |||||
| if (isBindSubmit) { | |||||
| $(this).submit(validate); | |||||
| } else { | |||||
| return validate(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| })(jQuery); | |||||
| @@ -0,0 +1,123 @@ | |||||
| function openTerminal(options) { | |||||
| //var CONNECT_TIME = 0; // 请求连接次数 | |||||
| Rows = parseInt(options.Rows); | |||||
| var client = new WSSHClient(); | |||||
| var base64 = new Base64(); | |||||
| var term = new Terminal({cols: 80, rows: Rows, screenKeys: true, useStyle: true}); | |||||
| // 发送客户端数据 | |||||
| term.on('data', function (data) { | |||||
| console.log("xterm data: "); | |||||
| console.log(data); | |||||
| client.sendClientData(data); | |||||
| }); | |||||
| term.open(); | |||||
| $('.terminal').detach().appendTo('#term'); | |||||
| $("#term").show(); | |||||
| term.write("Connecting..."); | |||||
| console.debug(options); | |||||
| //var interTime = setInterval(client_connect, 1000) | |||||
| setTimeout(client_connect, 3000); | |||||
| var intervalId = null; | |||||
| function client_connect() { | |||||
| // var TIMEINIT = 0; // 初始化时间 | |||||
| // var TIMEOUT = 60 * 15; // 超时时间 | |||||
| var CONNECTED = false; // 是否连接成功过 | |||||
| console.log("连接中...."); | |||||
| console.log(options); | |||||
| client.connect({ | |||||
| onError: function (error) { | |||||
| term.write('Error: ' + error + '\r\n'); | |||||
| console.log('error happened'); | |||||
| }, | |||||
| onConnect: function () { | |||||
| console.log('connection established'); | |||||
| // 连接上之后发送初始化数据 | |||||
| client.sendInitData(options); | |||||
| term.focus(); | |||||
| }, | |||||
| onClose: function () { | |||||
| console.log("连接关闭"); | |||||
| term.write("\r\nconnection closed"); | |||||
| if (CONNECTED) { | |||||
| console.log('connection reset by peer'); | |||||
| $('term').hide(); | |||||
| } | |||||
| }, | |||||
| // 当收到服务端返回的数据 | |||||
| onData: function (data) { | |||||
| if (!CONNECTED) { | |||||
| console.log("first connected."); | |||||
| term.write("\r"); //换行 | |||||
| term.focus(); //焦点移动到框上 | |||||
| } | |||||
| /*if(interTime){ | |||||
| clearInterval(interTime); | |||||
| }*/ | |||||
| CONNECTED = true; | |||||
| data = base64.decode(data); | |||||
| /* TIMEINIT = 0;*/ | |||||
| term.write(data); | |||||
| console.log('get data:' + data); | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| var charWidth = 6.2; | |||||
| var charHeight = 15.2; | |||||
| /** | |||||
| * for full screen | |||||
| * @returns {{w: number, h: number}} | |||||
| */ | |||||
| function getTerminalSize() { | |||||
| var width = window.innerWidth; | |||||
| var height = window.innerHeight; | |||||
| return { | |||||
| w: Math.floor(width / charWidth), | |||||
| h: Math.floor(height / charHeight) | |||||
| }; | |||||
| } | |||||
| function store(options) { | |||||
| window.localStorage.host = options.host | |||||
| window.localStorage.port = options.port | |||||
| window.localStorage.username = options.username | |||||
| window.localStorage.ispwd = options.ispwd | |||||
| window.localStorage.password = options.password | |||||
| } | |||||
| function check() { | |||||
| return validResult["host"] && validResult["port"] && validResult["username"]; | |||||
| } | |||||
| function connect() { | |||||
| var remember = $("#remember").is(":checked") | |||||
| var options = { | |||||
| host: $("#host").val(), | |||||
| port: $("#port").val(), | |||||
| username: $("#username").val(), | |||||
| password: $("#password").val(), | |||||
| Rows: $("#terminalRow").val(), | |||||
| } | |||||
| if (remember) { | |||||
| store(options) | |||||
| } | |||||
| if (true) { | |||||
| openTerminal(options) | |||||
| } else { | |||||
| for (var key in validResult) { | |||||
| if (!validResult[key]) { | |||||
| alert(errorMsg[key]); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| function WSSHClient() { | |||||
| }; | |||||
| WSSHClient.prototype._generateEndpoint = function () { | |||||
| if (window.location.protocol == 'https:') { | |||||
| var protocol = 'wss://'; | |||||
| } else { | |||||
| var protocol = 'ws://'; | |||||
| } | |||||
| var endpoint = protocol + window.location.host + '/ws'; | |||||
| return endpoint; | |||||
| }; | |||||
| WSSHClient.prototype.connect = function (options) { | |||||
| var endpoint = this._generateEndpoint(); | |||||
| if (window.WebSocket) { | |||||
| this._connection = new WebSocket(endpoint); | |||||
| } | |||||
| else if (window.MozWebSocket) { | |||||
| this._connection = MozWebSocket(endpoint); | |||||
| } | |||||
| else { | |||||
| options.onError('WebSocket Not Supported'); | |||||
| return; | |||||
| } | |||||
| this._connection.onopen = function () { | |||||
| options.onConnect(); | |||||
| }; | |||||
| this._connection.onmessage = function (evt) { | |||||
| var data = evt.data.toString() | |||||
| options.onData(data); | |||||
| }; | |||||
| this._connection.onclose = function (evt) { | |||||
| options.onClose(); | |||||
| }; | |||||
| }; | |||||
| WSSHClient.prototype.close = function () { | |||||
| this._connection.close(); | |||||
| }; | |||||
| WSSHClient.prototype.send = function (data) { | |||||
| this._connection.send(JSON.stringify(data)); | |||||
| }; | |||||
| WSSHClient.prototype.sendInitData = function (options) { | |||||
| var data = { | |||||
| hostname: options.host, | |||||
| port: options.port, | |||||
| username: options.username, | |||||
| password: options.password | |||||
| }; | |||||
| this._connection.send(JSON.stringify({"tp": "init", "data": options})) | |||||
| console.log("发送初始化数据:" + options) | |||||
| } | |||||
| WSSHClient.prototype.sendClientData = function (data) { | |||||
| this._connection.send(JSON.stringify({"tp": "client", "data": data})) | |||||
| console.log("发送客户端数据:" + data) | |||||
| } | |||||
| var client = new WSSHClient(); | |||||