Buy from Ali cloud server, domain name purchase, SSL free purchase.
A WebSocket of actual combat
1.1 know the WebSocket
The picture above shows
- Sending a connection request
The client initiates a WebSocket connection with the ws://host:port/ request address. WebSocket API is implemented by JavaScript to establish WebSocket connection with the server. Host INDICATES the server IP address. Port indicates the port.
- Shake hands
After receiving the request, the server parses the request header, determines whether the request is a WebSocket request based on the upgrade protocol, and retrieves the data of the SEC-Websocket-key field in the request information. Some algorithm generates a new sequence of strings in the request header sec-websocket-accept. Sec-websocket-accept: indicates that the server accepts the HTTP upgrade certificate from the client.
- Establish a WebSocket connection
After receiving the response from the server, the client also parses the request header and retrieves the SEC-websocket-Accept field. Use the same algorithm on the server side to process the previous SEC-websocket-accept data, and see the comparison between the processed and removed SEC-websocket-accept data. If the connection succeeds, the connection fails.
1.2 HTTP and WebSocket
The picture above shows
As you can see from the figure above, each HTTP request requires a connection. WebSocket is similar to a long link, once established, to subsequent data
It’s all in a sequence of frames.
- HTTP and WebSocket relationship
Similarities: HTTP and WebSocket are both reliable transport protocols and application layer protocols. Differences: WebSocket is a bidirectional communication protocol that simulates the Socket protocol and can send and receive data bidirectionally. HTTP is one-way.
- WebSocket establishes connection details
WebSocket establishes the handshake and the data is transmitted over HTTP. However, after establishing a connection, HTTP is not required for actual transmission. In WebSocket, after a handshake between the browser and the server, a SEPARATE TCP communication channel is established for data transfer.
- WebSocket advantages
Duplex communication replaces polling. You can do instant messaging and push messages.
1.3 SpringBoot integrates WebSocket
- Rely on
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
Copy the code
- Enable the WebSocket support endpoint
@Configuration public class Config { @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); }}Copy the code
- Create the Server core class
@Serverendpoint ("/ws/{userId}") @Component public class WebSocketServer {// Static Log Log = LogFactory.getLog(WebSocketServer.class); Private static final AtomicInteger onlineCount = new AtomicInteger(0); Private static Map<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>(); Private Session Session; Private String userId = ""; /* * @Description: */ @onOpen public void OnOpen (@pathParam ("userId") String userId, Session) {// Handle Session and user information this. Session = Session; this.userId = userId; if (webSocketMap.containsKey(userId)) { webSocketMap.remove(userId); webSocketMap.put(userId, this); } else { webSocketMap.put(userId, this); AddOnlineCount (); } try {// handle sending sendMessage("Server>>>> remote WebSoket connection successful "); Log.info (" user "+ userId +" successfully connected, the current number of online users is "+ getOnlineCount()); } catch (Exception e) { e.printStackTrace(); } } /* * @Description: */ @onClose public void OnClose () {if (webSocketmap.containsKey (userId)) {webSocketmap.remove (userId); subOnlineCount(); } log.info(" user logs out...." ); } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount.decrementAndGet(); } /* * @description: message transfer */ @onMessage public void OnMessage Session Session) {if (stringutils.isnotempty (message)) {try {// Parse the message JSONObject JSONObject = JSON.parseObject(message); String toUserId = jsonObject.getString("toUserId"); String msg = jsonObject.getString("msg"); if (StringUtils.isNotEmpty(toUserId) && webSocketMap.containsKey(toUserId)) { webSocketMap.get(toUserId).sendMessage(msg); } } catch (Exception e) { e.printStackTrace(); } } } /* * @Description: The server to the client to send data * / public void sendMessage (String s) throws IOException {this. Session. GetBasicRemote () sendText (s); } /* * @description: obtain the number of online */ public static synchronized AtomicInteger getOnlineCount() {return onlineCount; } /* * @Description: Increase the number of online * / public static synchronized void addOnlineCount () {WebSocketServer. OnlineCount. IncrementAndGet (); } /* * @Description: */ public static Boolean sendInfo(String message, @PathParam("userId") String userId) throws IOException { boolean flag=true; if (StringUtils.isNotEmpty(userId) && webSocketMap.containsKey(userId)) { webSocketMap.get(userId).sendMessage(message); } else {log.error(" user "+ userId +" not online "); flag=false; } return flag; }}Copy the code
- Creating a Controller
@RestController public class WebSocketController { @RequestMapping("im") public ModelAndView page() { return new ModelAndView("ws"); } /* * @Description: */ @requestMapping ("/push/{toUserId}") public ResponseEntity<String> pushToWeb(String message, @PathVariable String toUserId) throws Exception { boolean flag = WebSocketServer.sendInfo(message, toUserId); return flag == true ? Responseentity. ok(" Message pushed successfully...") ResponseEntity. Ok (" Notification push failed, user is not online...") : ResponseEntity. ); }}Copy the code
- Create message sending HTML
<! DOCTYPE html> <html> <head> <meta charset="utf-8"> <link href=".. "Rel ="/CSS/index. The CSS stylesheet "> < / head > < script SRC =" https://cdn.bootcss.com/jquery/3.3.1/jquery.js "> < / script > <script> var socket; Function openWebSocket() {if (typeof(WebSocket) == "undefined") {console.log(" sorry, your browser does not support WebSocket"); } else { var webSocketUrl = "ws://localhost/ws/" + $("#userId").val(); if (socket ! = null) { socket.close(); socket = null; } socket = new WebSocket(webSocketUrl); Onopen = function () {console.log("Client>>>>WebSocket opened "); }; Onmessage = function (MSG) {console.log(msg.data); $("#msg").val(msg.data) }; Onclose = function () {console.log("Client>>>>WebSocket closed "); }; Onerror = function () {console.log("Client>>>>WebSocket error "); }}} function sendMessage() {if (typeof(WebSocket) == "undefined") {console.log(" sorry, your browser does not support WebSocket"); } else { socket.send('{"toUserId":"' + $("#toUserId").val() + '","msg":"' + $("#msg").val() + '"}'); }} </script> <body> <div id="panel"> <div class="panel-header"> <h2> IM</h2> </div> <div class="panel-content"> <div Class ="user-pwd"> <button class="btn-user"> </button> <input id="userId" name="userId" type="text" value=" btn-user"> </div> <div class="user-pwd"> <button class="btn-user"> </button> <input id="toUserId" name="toUserId" type="text" value=" "> </div> <div class="user-msg"> <input id="msg" name="msg" type="text" value=""> </div> <div class="panel-footer"> <button Class ="login-btn" onclick="openWebSocket()" </button> <button class="login-btn" The onclick = "sendMessage ()" > send a message < / button > < / div > < / div > < / body > < / HTML >Copy the code
- Access path
http://localhost/im
Copy the code
- WebSocket connection request header analysis
WebSocket establishes a handshake connection using HTTP, which must be initiated by the browser. Relative to HTTP protocol more than a few things, tell Apache, Nginx and other servers, this launch is Websocket protocol.Copy the code
A GET does not request an address like HTTP /, but an address that begins with ws://; B Request headers Upgrade: webSocket and Connection: Upgrade indicate that this Connection will be converted to a webSocket Connection; C Sec-websocket-key is randomly generated by the browser to identify the connection, not to encrypt data; D sec-websocket-version Specifies the WebSocket protocol Version. E sec-websocket-extensions: request Extensions; An F code of 101 means that the server understands the client request.Copy the code
1.4 Self-signed Certificate HTTPS Development
1.4.1 Generating a keystore Certificate
- The KEYtool in the JDK is a certificate management tool that can generate self-signed certificates
keytool -genkey -alias czbk -keypass 123456 -keyalg RSA -keysize 1024 -validity
365 -keystore c:/czbk.keystore -storepass 123456
Copy the code
The command to explain
Keytool -genkey -alias Tomcat (alias) -keypass 123456 -keyalg RSA(algorithm name of the certificate, RSA is an asymmetric encryption algorithm. -keySize 1024(key length, certificate size) -validity 365(certificate validity period, Day unit) -keystore c:/czbk.keystore(specify the location and name of the certificate to be generated) -storepass 123456(password to obtain keystore information) -storetype (specify the keystore type)Copy the code
2. View the generated fileAnd copy the files to the Resources directory in the Spring Boot project.
- Configure the application. The properties
Server.ssl. key-store=classpath:czbk.keystore JKS server.ssl.key-store-type=JKS server.ssl.key-store-type=JKS server.ssl.key-store-type=JKS Server.ssl. key-alias= CZBK was set when the keystore was generatedCopy the code
- Adding configuration Classes
@Configuration public class HttpRedirectHttps { @Value("${http.port}") Integer httpPort; @Value("${server.port}") Integer httpsPort; /* * @description: HTTP redirect to HTTPS * @method: servletWebServerFactory * @param: [] * @update: * @since: 1.0.0 * @return: org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory * */ @Bean public TomcatServletWebServerFactory servletWebServerFactory() { TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory() { @Override protected void postProcessContext(Context context) { SecurityConstraint securityConstraint = new SecurityConstraint(); securityConstraint.setUserConstraint("CONFIDENTIAL"); SecurityCollection collection = new SecurityCollection(); collection.addPattern("/*"); securityConstraint.addCollection(collection); context.addConstraint(securityConstraint); }}; tomcatServletWebServerFactory.addAdditionalTomcatConnectors(createConnector()); return tomcatServletWebServerFactory; } public Connector createConnector() { Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); connector.setScheme("http"); connector.setPort(httpPort); connector.setSecure(false); connector.setRedirectPort(httpsPort); return connector; }}Copy the code
- Verify integration results
Jump to http://localhost:7777/im https://localhost:8888/imCopy the code
The image above shows that our self-generated HTTPS certificate is not recognized by Google Chrome. Just change the browser
1.5 Nginx proxy SSL
- benefits
Springboot programs do not need to add any SSL configuration. Resolve HTTP redirect HTTPS Resolve Wss service problems The Nginx version must be later than 1.3
- About nginx
Since version 1.3, Nginx has supported WebSocket and can do reverse proxy and load balancing for WebSocket applications. WebSocket and HTTP are two different protocols, but the handshake in WebSocket is compatible with the handshake in HTTP. It uses the Upgrade protocol header in HTTP to Upgrade the connection from HTTP to WebSocket. When the client sends a Connection: Upgrade request header, Nginx does not know about it. Therefore, when the Nginx proxy server intercepts an Upgrade request from a client, we need to explicitly configure the Connection, Upgrade header information, and use 101 (switching protocol) to return the response. Establish a tunnel between the client, proxy server, and back-end application service to support WebSocket.Copy the code
- Nginx process
Use Nginx reverse proxy to solve the WSS service problem of WebSocket, that is, the client connects to Nginx through WSS protocol and then Nginx communicates with the Server through Ws protocol. That is, Nginx is responsible for communication encryption and decryption, Nginx to the Server is clear text.Copy the code
Nginx configuration file
Line # * * * * * * * * * * * * * * * * * * * * * * * * * * events {worker_connections 1024; } line # * * * * * * * * * * * * * * * * * * * * * * * * * * HTTP map for {$http_upgrade $connection_upgrade {default upgrade; '' close; } line # * * * * * * * * * * * * * * * * * * * * * * * * * * server {listen 80; server_name websocket.nginx.com; add_header Strict-Transport-Security max-age=15768000; return 301 https://$server_name$request_uri; } line # * * * * * * * * * * * * * * * * * * * * * * * * * * server {443 SSL listen; server_name websocket.nginx.com ssl on; ssl_certificate c:\czbk.crt; ssl_certificate_key c:\czbk.key; ssl_session_timeout 5m; Ssl_protocols SSLv3 SSLv2 TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCMSHA256: ECDHE:ECDH:AES:HIGH:! NULL:! aNULL:! MD5:! ADH:! RC4; ssl_prefer_server_ciphers on; # * * * * * * * * page can also be used when the HTTP access/im * * * * * * * * * * * * * * * * * # the location/im / {# proxy_pass http://localhost:8888/im; #} # * * * * * * * * HTTP jump and WSS agreement calls (to be automatic judgment request protocol) * * * * * * * * * * * * * * * * * * the location / {proxy_pass http://localhost:8888; Proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header X-Real-IP $remote_addr; }} #******** secant ******************}Copy the code
The role of the MAP directive
$connection_upgrade = $connection_upgrade; $connection_upgrade = $connection_upgrade; # create rules that are inside {}. The rules are not matched, so the default, $connection_upgrade, will always be upgrade. Then, if $http_upgrade is an empty string, the value will be close.Copy the code
The core configuration
Ssl_certificate_key The private key is used for decryption, so its permissions are protected but can be read by the main nginx process. Ssl_session_timeout: The client can reuse the expiration time of SSL parameters in the session cacheCopy the code
The SSL_protocols directive is used to support encryption protocols
Ssl_ciphers selects an encryption suite. The suite (and order) supported by different browsers may differ. OpenSSL -v cipher 'RC4:HIGH:! aNULL:! MD5' (next is the suite encryption algorithm you specify) to see the supported algorithm. Encryption suites are separated by colons (:). The encryption suites preceded by an exclamation mark (!) must be discarded. Ssl_prefer_server_ciphers on Indicates the preferred encryption suite on the server over the browser on the client when setting the negotiated encryption algorithmCopy the code
Access to the process
1, 80 http://websocket.nginx.com/im/ access to listen; 443 443 443 443 443 443 443 443 443Copy the code
Reload the Nginx service
What are the main formats of digital certificates? In general, mainstream Web services software is based on two basic cryptographic libraries: OpenSSL and Java. Tomcat, Weblogic, and JBoss use the password library provided by Java. Use the Java Keytool to generate a Java Keystore (JKS) certificate file. Apache, Nginx, etc., using the password library provided by OpenSSL, Generate certificate files in the format of PEM, KEY, and CRT. In this step, the CRT and KEY files are required. Export the certificate kestoreCopy the code
2 aliyun HTTPS development
2.1 Preparations
- The preparatory work
A Ali Cloud ECS one b domain name one (www.itheima.cloud) c CA certificate one (used to support HTTPS) (domain name binding required) d Locally packaged Springboot project (packaged and uploaded to Ali Cloud) e FTP client one, Used to upload the JAR to ali Cloud serverCopy the code
- Aliyun domain name application
https://mi.aliyun.com/
Copy the code
3. Select a domain name to purchase 4. Check the domain name status
https://dc.console.aliyun.com/next/index? SPM = 5176.100251. Recommends. Ddomain. 6 ffe4f15tozyla # / domain/details/info? saleId=DT49H3EX462ZAYP&domain=itheima.cloudCopy the code
5. Whois results
6. Aliyun ECS server application
https://www.aliyun.com/activity/618/index? SPM = 5176.12825654. A9ylfrljh. D111. E9392c4aU65uab & SCM = 20140722.2188.2.2170Copy the code
7. If the payment is successful, view the ECS instance
https://ecs.console.aliyun.com/?spm=5176.2020520132.productsgrouped. decs. A1597218YHc0M5 # / server/region/cn - BeijingCopy the code
- Remote Login – reset password
After the ali cloud server is purchased, the new instance can operate normally only after the root login password is set; otherwise, it cannot be logged in. You can reset the login password of an instance only when the password is not set or the password is forgotten. For a running instance, restart the instance after the instance login password is reset for the new password to take effectCopy the code
9. Use an external IP address for connection
2.2 Alicloud SSL Certificate Application
https://common-buy.aliyun.com/? SPM = 5176.7968328.1266638.. 213d1232uExCSm&commodityCode=cas#/buyCopy the code
- Select SSL Certificate (Application Security)
2. Purchase a certificate
3. Buy free domain name (single domain name –DV/SSL– free version)
4. Start paying
5. The payment is successful
6. After successful payment
7. Click On certificate Application to “Bind certificate and domain name”
2.3 Bind the Domain name to the ECS Server
https://dns.console.aliyun.com/? SPM = 5176.100251.111252.22.3 a8b4f15HNfTsl # / DNS/setting/itheima. CloudCopy the code
- The DNS
2. Click OK to display WWW and @
2.4 SpringBoot is deployed on Aliyun
- Ali Cloud remote deployment
2. Modify the configuration file (the certificate path must be classpath)
HTTP: port: 7777 Server: # port using HTTPS Default port: 443 #HTTPS encryption configuration SSL: # certificate path key-store: Classpath :4108720_www.itheima.cloud.pfx # Certificate password key-store-password: 5nLB0iXqCopy the code
- Centos7 Kills processes that occupy ports
# according to the port number to get its take up the process of the details of the netstat LNP | grep process of 443 # to check the details of the ps "pid" # directly kill takes up the process of port - 9 is closed kill 9 "pid" or netstat 443 jobs - tunlp | grep view nohup running program ps - ef | grep Java kill 9 idCopy the code
- Package the SpringBoot project (using Packge for IDEA) and start it
Nohup java-jar itheima-websocket-aliyun-https- 1.0-snapshot.jar &Copy the code
- Accessing aliyun HTTPS application (Security group configuration)
https://ecs.console.aliyun.com/?spm=a2c1d.8251892.recommends.decs.10565b76fQAfCc#/ser
ver/region/cn-beijing
Copy the code
IP access http://101.201.232.138:7777/im domain name to https://www.itheima.cloud/imCopy the code
Note:
The first time the certificate is deployed, it is accessible, and after about two minutes of filtering, it is detected by the Ari Cloud and then it is not accessible because the domain name has not been registered and it is transferred to HTTP access, and there is a message indicating that the domain name is not registeredCopy the code