Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
S
spring-boot
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DEMO
spring-boot
Commits
b75c79cc
Commit
b75c79cc
authored
Jun 16, 2021
by
Phillip Webb
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch '2.4.x' into 2.5.x
Closes gh-26919
parents
2c2ab693
93fd7c62
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
113 additions
and
10 deletions
+113
-10
Connection.java
.../springframework/boot/devtools/livereload/Connection.java
+11
-9
LiveReloadServerTests.java
...ework/boot/devtools/livereload/LiveReloadServerTests.java
+102
-1
No files found.
spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/livereload/Connection.java
View file @
b75c79cc
/*
/*
* Copyright 2012-20
19
the original author or authors.
* Copyright 2012-20
21
the original author or authors.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* you may not use this file except in compliance with the License.
...
@@ -23,6 +23,7 @@ import java.net.Socket;
...
@@ -23,6 +23,7 @@ import java.net.Socket;
import
java.net.SocketTimeoutException
;
import
java.net.SocketTimeoutException
;
import
java.security.MessageDigest
;
import
java.security.MessageDigest
;
import
java.security.NoSuchAlgorithmException
;
import
java.security.NoSuchAlgorithmException
;
import
java.util.Locale
;
import
java.util.regex.Matcher
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
import
java.util.regex.Pattern
;
...
@@ -30,18 +31,20 @@ import org.apache.commons.logging.Log;
...
@@ -30,18 +31,20 @@ import org.apache.commons.logging.Log;
import
org.apache.commons.logging.LogFactory
;
import
org.apache.commons.logging.LogFactory
;
import
org.springframework.core.log.LogMessage
;
import
org.springframework.core.log.LogMessage
;
import
org.springframework.util.Assert
;
import
org.springframework.util.Base64Utils
;
import
org.springframework.util.Base64Utils
;
/**
/**
* A {@link LiveReloadServer} connection.
* A {@link LiveReloadServer} connection.
*
*
* @author Phillip Webb
* @author Phillip Webb
* @author Francis Lavoie
*/
*/
class
Connection
{
class
Connection
{
private
static
final
Log
logger
=
LogFactory
.
getLog
(
Connection
.
class
);
private
static
final
Log
logger
=
LogFactory
.
getLog
(
Connection
.
class
);
private
static
final
Pattern
WEBSOCKET_KEY_PATTERN
=
Pattern
.
compile
(
"^
Sec-WebSocket-K
ey:(.*)$"
,
Pattern
.
MULTILINE
);
private
static
final
Pattern
WEBSOCKET_KEY_PATTERN
=
Pattern
.
compile
(
"^
sec-websocket-k
ey:(.*)$"
,
Pattern
.
MULTILINE
);
public
static
final
String
WEBSOCKET_GUID
=
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
;
public
static
final
String
WEBSOCKET_GUID
=
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
;
...
@@ -68,8 +71,9 @@ class Connection {
...
@@ -68,8 +71,9 @@ class Connection {
this
.
socket
=
socket
;
this
.
socket
=
socket
;
this
.
inputStream
=
new
ConnectionInputStream
(
inputStream
);
this
.
inputStream
=
new
ConnectionInputStream
(
inputStream
);
this
.
outputStream
=
new
ConnectionOutputStream
(
outputStream
);
this
.
outputStream
=
new
ConnectionOutputStream
(
outputStream
);
this
.
header
=
this
.
inputStream
.
readHeader
();
String
header
=
this
.
inputStream
.
readHeader
();
logger
.
debug
(
LogMessage
.
format
(
"Established livereload connection [%s]"
,
this
.
header
));
logger
.
debug
(
LogMessage
.
format
(
"Established livereload connection [%s]"
,
header
));
this
.
header
=
header
.
toLowerCase
(
Locale
.
ENGLISH
);
}
}
/**
/**
...
@@ -77,10 +81,10 @@ class Connection {
...
@@ -77,10 +81,10 @@ class Connection {
* @throws Exception in case of errors
* @throws Exception in case of errors
*/
*/
void
run
()
throws
Exception
{
void
run
()
throws
Exception
{
if
(
this
.
header
.
contains
(
"
Upgrade: websocket"
)
&&
this
.
header
.
contains
(
"Sec-WebSocket-V
ersion: 13"
))
{
if
(
this
.
header
.
contains
(
"
upgrade: websocket"
)
&&
this
.
header
.
contains
(
"sec-websocket-v
ersion: 13"
))
{
runWebSocket
();
runWebSocket
();
}
}
if
(
this
.
header
.
contains
(
"
GET
/livereload.js"
))
{
if
(
this
.
header
.
contains
(
"
get
/livereload.js"
))
{
this
.
outputStream
.
writeHttp
(
getClass
().
getResourceAsStream
(
"livereload.js"
),
"text/javascript"
);
this
.
outputStream
.
writeHttp
(
getClass
().
getResourceAsStream
(
"livereload.js"
),
"text/javascript"
);
}
}
}
}
...
@@ -140,9 +144,7 @@ class Connection {
...
@@ -140,9 +144,7 @@ class Connection {
private
String
getWebsocketAcceptResponse
()
throws
NoSuchAlgorithmException
{
private
String
getWebsocketAcceptResponse
()
throws
NoSuchAlgorithmException
{
Matcher
matcher
=
WEBSOCKET_KEY_PATTERN
.
matcher
(
this
.
header
);
Matcher
matcher
=
WEBSOCKET_KEY_PATTERN
.
matcher
(
this
.
header
);
if
(!
matcher
.
find
())
{
Assert
.
state
(
matcher
.
find
(),
"No Sec-WebSocket-Key"
);
throw
new
IllegalStateException
(
"No Sec-WebSocket-Key"
);
}
String
response
=
matcher
.
group
(
1
).
trim
()
+
WEBSOCKET_GUID
;
String
response
=
matcher
.
group
(
1
).
trim
()
+
WEBSOCKET_GUID
;
MessageDigest
messageDigest
=
MessageDigest
.
getInstance
(
"SHA-1"
);
MessageDigest
messageDigest
=
MessageDigest
.
getInstance
(
"SHA-1"
);
messageDigest
.
update
(
response
.
getBytes
(),
0
,
response
.
length
());
messageDigest
.
update
(
response
.
getBytes
(),
0
,
response
.
length
());
...
...
spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/livereload/LiveReloadServerTests.java
View file @
b75c79cc
...
@@ -19,13 +19,27 @@ package org.springframework.boot.devtools.livereload;
...
@@ -19,13 +19,27 @@ package org.springframework.boot.devtools.livereload;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStream
;
import
java.io.OutputStream
;
import
java.io.OutputStream
;
import
java.net.InetAddress
;
import
java.net.InetSocketAddress
;
import
java.net.URI
;
import
java.net.URI
;
import
java.net.UnknownHostException
;
import
java.time.Duration
;
import
java.time.Duration
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Objects
;
import
java.util.Objects
;
import
java.util.concurrent.Callable
;
import
java.util.concurrent.CountDownLatch
;
import
java.util.concurrent.CountDownLatch
;
import
java.util.concurrent.TimeUnit
;
import
java.util.concurrent.TimeUnit
;
import
java.util.function.Function
;
import
java.util.stream.Collectors
;
import
javax.websocket.ClientEndpointConfig
;
import
javax.websocket.ClientEndpointConfig.Configurator
;
import
javax.websocket.Endpoint
;
import
javax.websocket.HandshakeResponse
;
import
javax.websocket.WebSocketContainer
;
import
org.apache.tomcat.websocket.WsWebSocketContainer
;
import
org.apache.tomcat.websocket.WsWebSocketContainer
;
import
org.awaitility.Awaitility
;
import
org.awaitility.Awaitility
;
...
@@ -34,13 +48,20 @@ import org.junit.jupiter.api.BeforeEach;
...
@@ -34,13 +48,20 @@ import org.junit.jupiter.api.BeforeEach;
import
org.junit.jupiter.api.Disabled
;
import
org.junit.jupiter.api.Disabled
;
import
org.junit.jupiter.api.Test
;
import
org.junit.jupiter.api.Test
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.util.concurrent.ListenableFuture
;
import
org.springframework.web.client.RestTemplate
;
import
org.springframework.web.client.RestTemplate
;
import
org.springframework.web.socket.CloseStatus
;
import
org.springframework.web.socket.CloseStatus
;
import
org.springframework.web.socket.PingMessage
;
import
org.springframework.web.socket.PingMessage
;
import
org.springframework.web.socket.PongMessage
;
import
org.springframework.web.socket.PongMessage
;
import
org.springframework.web.socket.TextMessage
;
import
org.springframework.web.socket.TextMessage
;
import
org.springframework.web.socket.WebSocketExtension
;
import
org.springframework.web.socket.WebSocketHandler
;
import
org.springframework.web.socket.WebSocketMessage
;
import
org.springframework.web.socket.WebSocketMessage
;
import
org.springframework.web.socket.WebSocketSession
;
import
org.springframework.web.socket.WebSocketSession
;
import
org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter
;
import
org.springframework.web.socket.adapter.standard.StandardWebSocketSession
;
import
org.springframework.web.socket.adapter.standard.WebSocketToStandardExtensionAdapter
;
import
org.springframework.web.socket.client.WebSocketClient
;
import
org.springframework.web.socket.client.WebSocketClient
;
import
org.springframework.web.socket.client.standard.StandardWebSocketClient
;
import
org.springframework.web.socket.client.standard.StandardWebSocketClient
;
import
org.springframework.web.socket.handler.TextWebSocketHandler
;
import
org.springframework.web.socket.handler.TextWebSocketHandler
;
...
@@ -94,7 +115,16 @@ class LiveReloadServerTests {
...
@@ -94,7 +115,16 @@ class LiveReloadServerTests {
(
msgs
)
->
msgs
.
size
()
==
2
);
(
msgs
)
->
msgs
.
size
()
==
2
);
assertThat
(
messages
.
get
(
0
)).
contains
(
"http://livereload.com/protocols/official-7"
);
assertThat
(
messages
.
get
(
0
)).
contains
(
"http://livereload.com/protocols/official-7"
);
assertThat
(
messages
.
get
(
1
)).
contains
(
"command\":\"reload\""
);
assertThat
(
messages
.
get
(
1
)).
contains
(
"command\":\"reload\""
);
}
@Test
// gh-26813
void
triggerReloadWithUppercaseHeaders
()
throws
Exception
{
LiveReloadWebSocketHandler
handler
=
connect
(
UppercaseWebSocketClient:
:
new
);
this
.
server
.
triggerReload
();
List
<
String
>
messages
=
await
().
atMost
(
Duration
.
ofSeconds
(
10
)).
until
(
handler:
:
getMessages
,
(
msgs
)
->
msgs
.
size
()
==
2
);
assertThat
(
messages
.
get
(
0
)).
contains
(
"http://livereload.com/protocols/official-7"
);
assertThat
(
messages
.
get
(
1
)).
contains
(
"command\":\"reload\""
);
}
}
@Test
@Test
...
@@ -126,7 +156,13 @@ class LiveReloadServerTests {
...
@@ -126,7 +156,13 @@ class LiveReloadServerTests {
}
}
private
LiveReloadWebSocketHandler
connect
()
throws
Exception
{
private
LiveReloadWebSocketHandler
connect
()
throws
Exception
{
WebSocketClient
client
=
new
StandardWebSocketClient
(
new
WsWebSocketContainer
());
return
connect
(
StandardWebSocketClient:
:
new
);
}
private
LiveReloadWebSocketHandler
connect
(
Function
<
WebSocketContainer
,
WebSocketClient
>
clientFactory
)
throws
Exception
{
WsWebSocketContainer
webSocketContainer
=
new
WsWebSocketContainer
();
WebSocketClient
client
=
clientFactory
.
apply
(
webSocketContainer
);
LiveReloadWebSocketHandler
handler
=
new
LiveReloadWebSocketHandler
();
LiveReloadWebSocketHandler
handler
=
new
LiveReloadWebSocketHandler
();
client
.
doHandshake
(
handler
,
"ws://localhost:"
+
this
.
port
+
"/livereload"
);
client
.
doHandshake
(
handler
,
"ws://localhost:"
+
this
.
port
+
"/livereload"
);
handler
.
awaitHello
();
handler
.
awaitHello
();
...
@@ -246,4 +282,69 @@ class LiveReloadServerTests {
...
@@ -246,4 +282,69 @@ class LiveReloadServerTests {
}
}
static
class
UppercaseWebSocketClient
extends
StandardWebSocketClient
{
private
final
WebSocketContainer
webSocketContainer
;
UppercaseWebSocketClient
(
WebSocketContainer
webSocketContainer
)
{
super
(
webSocketContainer
);
this
.
webSocketContainer
=
webSocketContainer
;
}
@Override
protected
ListenableFuture
<
WebSocketSession
>
doHandshakeInternal
(
WebSocketHandler
webSocketHandler
,
HttpHeaders
headers
,
URI
uri
,
List
<
String
>
protocols
,
List
<
WebSocketExtension
>
extensions
,
Map
<
String
,
Object
>
attributes
)
{
InetSocketAddress
localAddress
=
new
InetSocketAddress
(
getLocalHost
(),
uri
.
getPort
());
InetSocketAddress
remoteAddress
=
new
InetSocketAddress
(
uri
.
getHost
(),
uri
.
getPort
());
StandardWebSocketSession
session
=
new
StandardWebSocketSession
(
headers
,
attributes
,
localAddress
,
remoteAddress
);
ClientEndpointConfig
endpointConfig
=
ClientEndpointConfig
.
Builder
.
create
()
.
configurator
(
new
UppercaseWebSocketClientConfigurator
(
headers
)).
preferredSubprotocols
(
protocols
)
.
extensions
(
extensions
.
stream
().
map
(
WebSocketToStandardExtensionAdapter:
:
new
)
.
collect
(
Collectors
.
toList
()))
.
build
();
endpointConfig
.
getUserProperties
().
putAll
(
getUserProperties
());
Endpoint
endpoint
=
new
StandardWebSocketHandlerAdapter
(
webSocketHandler
,
session
);
Callable
<
WebSocketSession
>
connectTask
=
()
->
{
this
.
webSocketContainer
.
connectToServer
(
endpoint
,
endpointConfig
,
uri
);
return
session
;
};
return
getTaskExecutor
().
submitListenable
(
connectTask
);
}
private
InetAddress
getLocalHost
()
{
try
{
return
InetAddress
.
getLocalHost
();
}
catch
(
UnknownHostException
ex
)
{
return
InetAddress
.
getLoopbackAddress
();
}
}
}
private
static
class
UppercaseWebSocketClientConfigurator
extends
Configurator
{
private
final
HttpHeaders
headers
;
UppercaseWebSocketClientConfigurator
(
HttpHeaders
headers
)
{
this
.
headers
=
headers
;
}
@Override
public
void
beforeRequest
(
Map
<
String
,
List
<
String
>>
requestHeaders
)
{
Map
<
String
,
List
<
String
>>
uppercaseRequestHeaders
=
new
LinkedHashMap
<>();
requestHeaders
.
forEach
((
key
,
value
)
->
uppercaseRequestHeaders
.
put
(
key
.
toUpperCase
(),
value
));
requestHeaders
.
clear
();
requestHeaders
.
putAll
(
uppercaseRequestHeaders
);
requestHeaders
.
putAll
(
this
.
headers
);
}
@Override
public
void
afterResponse
(
HandshakeResponse
response
)
{
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment