Repository: netty/netty
Issue: WebSocketClientHandshaker java.lang.NullPointerException

WebSocketClientHandshaker 
![1666764902075](https://user-images.githubusercontent.com/10923974/197948891-0bc9490a-8b02-4d0f-bc49-506fcb1a0d11.png)

Issue comments:

Missing: header-> origin
Please explain in more detail.
When the websocketclient connection request header is missing the origin, a null pointer problem will occur
@amizurov What's your take? Do we need validation to check for the origin header somewhere?
Now he throws a null pointer. I think we can give some warning logs
Hi @heixiaoma , @chrisvest. Let me take a look.
@heixiaoma  Could you please clarify, did you set `webSocketURL` -> "/ws" via constructor and `customHeaders` which contains `Host` but does not contain `Origin` header ?
> @heixiaoma 您能否澄清一下，您是否`webSocketURL`通过构造函数设置了 -> "/ws" 并且`customHeaders`包含`Host`但不包含`Origin`header ？

yes
So the main problem here, that if we pass the `webSocketURL` without host (e.g. '/ws') we faced with NPE even when we building the `Host` header value. But if we pass the `Host` header value via `customHeaders` everything should works fine, but now we got the NPE on the building `Origin` header value (see https://github.com/netty/netty/issues/9673). 

I created the PR which should fix the `Origin` header behaviour (https://github.com/netty/netty/pull/12941). Also i think we can add additional check that if `webSocketURL` does not contain host and `Host` header does not present in custom headers we throw an invalid arg exception. @chrisvest WDYT ?


@amizurov Sounds good. Do you want to add that check to https://github.com/netty/netty/pull/12941, or in a different PR?
> @amizurov Sounds good. Do you want to add that check to https://github.com/netty/netty/pull/12941, or in a different PR?

Added to https://github.com/netty/netty/pull/12941
I think this was fixed by #12941

Pull request review comments:
This is an odd usage of assertThrows:

```suggestion
        assertInstanceOf(IllegalArgumentException.class,  handshakeFuture.cause());
```
This is an odd usage of assertThrows:

```suggestion
        assertInstanceOf(IllegalArgumentException.class,  handshakeFuture.cause());
```
```
Done
Done

Patch that resolved the issue:
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java
index e5ff31c6d94..2df036ac0a8 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java
@@ -85,6 +85,8 @@ public abstract class WebSocketClientHandshaker {
 
     private final boolean absoluteUpgradeUrl;
 
+    protected final boolean generateOriginHeader;
+
     /**
      * Base constructor
      *
@@ -151,6 +153,36 @@ protected WebSocketClientHandshaker(URI uri, WebSocketVersion version, String su
     protected WebSocketClientHandshaker(URI uri, WebSocketVersion version, String subprotocol,
                                         HttpHeaders customHeaders, int maxFramePayloadLength,
                                         long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) {
+        this(uri, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis,
+                absoluteUpgradeUrl, true);
+    }
+
+    /**
+     * Base constructor
+     *
+     * @param uri
+     *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
+     *            sent to this URL.
+     * @param version
+     *            Version of web socket specification to use to connect to the server
+     * @param subprotocol
+     *            Sub protocol request sent to the server.
+     * @param customHeaders
+     *            Map of custom headers to add to the client request
+     * @param maxFramePayloadLength
+     *            Maximum length of a frame's payload
+     * @param forceCloseTimeoutMillis
+     *            Close the connection if it was not closed by the server after timeout specified
+     * @param  absoluteUpgradeUrl
+     *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
+     *            clear HTTP
+     * @param generateOriginHeader
+     *            Allows to generate the `Origin`|`Sec-WebSocket-Origin` header value for handshake request
+     *            according to the given webSocketURL
+     */
+    protected WebSocketClientHandshaker(URI uri, WebSocketVersion version, String subprotocol,
+            HttpHeaders customHeaders, int maxFramePayloadLength,
+            long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl, boolean generateOriginHeader) {
         this.uri = uri;
         this.version = version;
         expectedSubprotocol = subprotocol;
@@ -158,6 +190,7 @@ protected WebSocketClientHandshaker(URI uri, WebSocketVersion version, String su
         this.maxFramePayloadLength = maxFramePayloadLength;
         this.forceCloseTimeoutMillis = forceCloseTimeoutMillis;
         this.absoluteUpgradeUrl = absoluteUpgradeUrl;
+        this.generateOriginHeader = generateOriginHeader;
     }
 
     /**
@@ -265,6 +298,28 @@ public final ChannelFuture handshake(Channel channel, final ChannelPromise promi
             }
         }
 
+        if (uri.getHost() == null) {
+            if (customHeaders == null || !customHeaders.contains(HttpHeaderNames.HOST)) {
+                promise.setFailure(new IllegalArgumentException("Cannot generate the 'host' header value," +
+                        " webSocketURI should contain host or passed through customHeaders"));
+                return promise;
+            }
+
+            if (generateOriginHeader && !customHeaders.contains(HttpHeaderNames.ORIGIN)) {
+                final String originName;
+                if (version == WebSocketVersion.V07 || version == WebSocketVersion.V08) {
+                    originName = HttpHeaderNames.SEC_WEBSOCKET_ORIGIN.toString();
+                } else {
+                    originName = HttpHeaderNames.ORIGIN.toString();
+                }
+
+                promise.setFailure(new IllegalArgumentException("Cannot generate the '" + originName + "' header" +
+                        " value, webSocketURI should contain host or disable generateOriginHeader or pass value" +
+                        " through customHeaders"));
+                return promise;
+            }
+        }
+
         FullHttpRequest request = newHandshakeRequest();
 
         channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java
index 9ccee77af1c..6b63e38f24f 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java
@@ -109,11 +109,42 @@ public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, S
      *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
      *            clear HTTP
      */
+    WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol,
+            HttpHeaders customHeaders, int maxFramePayloadLength,
+            long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) {
+        this(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis,
+                absoluteUpgradeUrl, true);
+    }
+
+    /**
+     * Creates a new instance with the specified destination WebSocket location and version to initiate.
+     *
+     * @param webSocketURL
+     *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
+     *            sent to this URL.
+     * @param version
+     *            Version of web socket specification to use to connect to the server
+     * @param subprotocol
+     *            Sub protocol request sent to the server.
+     * @param customHeaders
+     *            Map of custom headers to add to the client request
+     * @param maxFramePayloadLength
+     *            Maximum length of a frame's payload
+     * @param forceCloseTimeoutMillis
+     *            Close the connection if it was not closed by the server after timeout specified
+     * @param  absoluteUpgradeUrl
+     *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
+     *            clear HTTP
+     * @param generateOriginHeader
+     *            Allows to generate the `Origin` header value for handshake request
+     *            according to the given webSocketURL
+     */
     WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol,
                                 HttpHeaders customHeaders, int maxFramePayloadLength,
-                                long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) {
+                                long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl,
+                                boolean generateOriginHeader) {
         super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis,
-                absoluteUpgradeUrl);
+                absoluteUpgradeUrl, generateOriginHeader);
     }
 
     /**
@@ -182,15 +213,22 @@ protected FullHttpRequest newHandshakeRequest() {
 
         if (customHeaders != null) {
             headers.add(customHeaders);
+            if (!headers.contains(HttpHeaderNames.HOST)) {
+                // Only add HOST header if customHeaders did not contain it.
+                //
+                // See https://github.com/netty/netty/issues/10101
+                headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL));
+            }
+        } else {
+            headers.set(HttpHeaderNames.HOST, websocketHostValue(wsURL));
         }
 
         headers.set(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET)
                .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
-               .set(HttpHeaderNames.HOST, websocketHostValue(wsURL))
                .set(HttpHeaderNames.SEC_WEBSOCKET_KEY1, key1)
                .set(HttpHeaderNames.SEC_WEBSOCKET_KEY2, key2);
 
-        if (!headers.contains(HttpHeaderNames.ORIGIN)) {
+        if (generateOriginHeader && !headers.contains(HttpHeaderNames.ORIGIN)) {
             headers.set(HttpHeaderNames.ORIGIN, websocketOriginValue(wsURL));
         }
 
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java
index 924978a256b..b0d1ace3628 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java
@@ -164,12 +164,52 @@ public WebSocketClientHandshaker07(URI webSocketURL, WebSocketVersion version, S
      *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
      *            clear HTTP
      */
+    WebSocketClientHandshaker07(URI webSocketURL, WebSocketVersion version, String subprotocol,
+            boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
+            boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis,
+            boolean absoluteUpgradeUrl) {
+        this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking,
+                allowMaskMismatch, forceCloseTimeoutMillis, absoluteUpgradeUrl, true);
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @param webSocketURL
+     *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
+     *            sent to this URL.
+     * @param version
+     *            Version of web socket specification to use to connect to the server
+     * @param subprotocol
+     *            Sub protocol request sent to the server.
+     * @param allowExtensions
+     *            Allow extensions to be used in the reserved bits of the web socket frame
+     * @param customHeaders
+     *            Map of custom headers to add to the client request
+     * @param maxFramePayloadLength
+     *            Maximum length of a frame's payload
+     * @param performMasking
+     *            Whether to mask all written websocket frames. This must be set to true in order to be fully compatible
+     *            with the websocket specifications. Client applications that communicate with a non-standard server
+     *            which doesn't require masking might set this to false to achieve a higher performance.
+     * @param allowMaskMismatch
+     *            When set to true, frames which are not masked properly according to the standard will still be
+     *            accepted
+     * @param forceCloseTimeoutMillis
+     *            Close the connection if it was not closed by the server after timeout specified.
+     * @param  absoluteUpgradeUrl
+     *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
+     *            clear HTTP
+     * @param generateOriginHeader
+     *            Allows to generate a `Sec-WebSocket-Origin` header value for handshake request
+     *            according to the given webSocketURL
+     */
     WebSocketClientHandshaker07(URI webSocketURL, WebSocketVersion version, String subprotocol,
                                 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
                                 boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis,
-                                boolean absoluteUpgradeUrl) {
+                                boolean absoluteUpgradeUrl, boolean generateOriginHeader) {
         super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis,
-                absoluteUpgradeUrl);
+                absoluteUpgradeUrl, generateOriginHeader);
         this.allowExtensions = allowExtensions;
         this.performMasking = performMasking;
         this.allowMaskMismatch = allowMaskMismatch;
@@ -232,7 +272,7 @@ protected FullHttpRequest newHandshakeRequest() {
                .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
                .set(HttpHeaderNames.SEC_WEBSOCKET_KEY, key);
 
-        if (!headers.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN)) {
+        if (generateOriginHeader && !headers.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN)) {
             headers.set(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN, websocketOriginValue(wsURL));
         }
 
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java
index f24e7c469ad..8f2f7b7c0cd 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java
@@ -134,7 +134,7 @@ public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, S
             boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
             boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis) {
         this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking,
-                allowMaskMismatch, forceCloseTimeoutMillis, false);
+                allowMaskMismatch, forceCloseTimeoutMillis, false, true);
     }
 
     /**
@@ -166,12 +166,52 @@ public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, S
      *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
      *            clear HTTP
      */
+    WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol,
+            boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
+            boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis,
+            boolean absoluteUpgradeUrl) {
+        this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking,
+                allowMaskMismatch, forceCloseTimeoutMillis, absoluteUpgradeUrl, true);
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @param webSocketURL
+     *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
+     *            sent to this URL.
+     * @param version
+     *            Version of web socket specification to use to connect to the server
+     * @param subprotocol
+     *            Sub protocol request sent to the server.
+     * @param allowExtensions
+     *            Allow extensions to be used in the reserved bits of the web socket frame
+     * @param customHeaders
+     *            Map of custom headers to add to the client request
+     * @param maxFramePayloadLength
+     *            Maximum length of a frame's payload
+     * @param performMasking
+     *            Whether to mask all written websocket frames. This must be set to true in order to be fully compatible
+     *            with the websocket specifications. Client applications that communicate with a non-standard server
+     *            which doesn't require masking might set this to false to achieve a higher performance.
+     * @param allowMaskMismatch
+     *            When set to true, frames which are not masked properly according to the standard will still be
+     *            accepted
+     * @param forceCloseTimeoutMillis
+     *            Close the connection if it was not closed by the server after timeout specified.
+     * @param  absoluteUpgradeUrl
+     *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
+     *            clear HTTP
+     * @param generateOriginHeader
+     *            Allows to generate a `Sec-WebSocket-Origin` header value for handshake request
+     *            according to the given webSocketURL
+     */
     WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol,
                                 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
                                 boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis,
-                                boolean absoluteUpgradeUrl) {
+                                boolean absoluteUpgradeUrl, boolean generateOriginHeader) {
         super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis,
-                absoluteUpgradeUrl);
+                absoluteUpgradeUrl, generateOriginHeader);
         this.allowExtensions = allowExtensions;
         this.performMasking = performMasking;
         this.allowMaskMismatch = allowMaskMismatch;
@@ -234,7 +274,7 @@ protected FullHttpRequest newHandshakeRequest() {
                .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
                .set(HttpHeaderNames.SEC_WEBSOCKET_KEY, key);
 
-        if (!headers.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN)) {
+        if (generateOriginHeader && !headers.contains(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN)) {
             headers.set(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN, websocketOriginValue(wsURL));
         }
 
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java
index 70805611fa5..6e47607c02e 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java
@@ -167,12 +167,53 @@ public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, S
      *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
      *            clear HTTP
      */
+    WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
+            boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
+            boolean performMasking, boolean allowMaskMismatch,
+            long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) {
+        this(webSocketURL, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, performMasking,
+                allowMaskMismatch, forceCloseTimeoutMillis, absoluteUpgradeUrl, true);
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @param webSocketURL
+     *            URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
+     *            sent to this URL.
+     * @param version
+     *            Version of web socket specification to use to connect to the server
+     * @param subprotocol
+     *            Sub protocol request sent to the server.
+     * @param allowExtensions
+     *            Allow extensions to be used in the reserved bits of the web socket frame
+     * @param customHeaders
+     *            Map of custom headers to add to the client request
+     * @param maxFramePayloadLength
+     *            Maximum length of a frame's payload
+     * @param performMasking
+     *            Whether to mask all written websocket frames. This must be set to true in order to be fully compatible
+     *            with the websocket specifications. Client applications that communicate with a non-standard server
+     *            which doesn't require masking might set this to false to achieve a higher performance.
+     * @param allowMaskMismatch
+     *            When set to true, frames which are not masked properly according to the standard will still be
+     *            accepted
+     * @param forceCloseTimeoutMillis
+     *            Close the connection if it was not closed by the server after timeout specified.
+     * @param  absoluteUpgradeUrl
+     *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
+     *            clear HTTP
+     * @param generateOriginHeader
+     *            Allows to generate the `Origin` header value for handshake request
+     *            according to the given webSocketURL
+     */
     WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
                                 boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
                                 boolean performMasking, boolean allowMaskMismatch,
-                                long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl) {
+                                long forceCloseTimeoutMillis, boolean absoluteUpgradeUrl,
+                                boolean generateOriginHeader) {
         super(webSocketURL, version, subprotocol, customHeaders, maxFramePayloadLength, forceCloseTimeoutMillis,
-                absoluteUpgradeUrl);
+                absoluteUpgradeUrl, generateOriginHeader);
         this.allowExtensions = allowExtensions;
         this.performMasking = performMasking;
         this.allowMaskMismatch = allowMaskMismatch;
@@ -190,7 +231,6 @@ public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, S
      * Upgrade: websocket
      * Connection: Upgrade
      * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
-     * Origin: http://example.com
      * Sec-WebSocket-Protocol: chat, superchat
      * Sec-WebSocket-Version: 13
      * </pre>
@@ -235,7 +275,7 @@ protected FullHttpRequest newHandshakeRequest() {
                .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE)
                .set(HttpHeaderNames.SEC_WEBSOCKET_KEY, key);
 
-        if (!headers.contains(HttpHeaderNames.ORIGIN)) {
+        if (generateOriginHeader && !headers.contains(HttpHeaderNames.ORIGIN)) {
             headers.set(HttpHeaderNames.ORIGIN, websocketOriginValue(wsURL));
         }
 
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
index a5dabdcbd55..ce915eb3986 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerFactory.java
@@ -222,4 +222,69 @@ public static WebSocketClientHandshaker newHandshaker(
 
         throw new WebSocketClientHandshakeException("Protocol version " + version + " not supported.");
     }
+
+    /**
+     * Creates a new handshaker.
+     *
+     * @param webSocketURL
+     *            URL for web socket communications. e.g "ws://myhost.com/mypath".
+     *            Subsequent web socket frames will be sent to this URL.
+     * @param version
+     *            Version of web socket specification to use to connect to the server
+     * @param subprotocol
+     *            Sub protocol request sent to the server. Null if no sub-protocol support is required.
+     * @param allowExtensions
+     *            Allow extensions to be used in the reserved bits of the web socket frame
+     * @param customHeaders
+     *            Custom HTTP headers to send during the handshake
+     * @param maxFramePayloadLength
+     *            Maximum allowable frame payload length. Setting this value to your application's
+     *            requirement may reduce denial of service attacks using long data frames.
+     * @param performMasking
+     *            Whether to mask all written websocket frames. This must be set to true in order to be fully compatible
+     *            with the websocket specifications. Client applications that communicate with a non-standard server
+     *            which doesn't require masking might set this to false to achieve a higher performance.
+     * @param allowMaskMismatch
+     *            When set to true, frames which are not masked properly according to the standard will still be
+     *            accepted.
+     * @param forceCloseTimeoutMillis
+     *            Close the connection if it was not closed by the server after timeout specified
+     * @param  absoluteUpgradeUrl
+     *            Use an absolute url for the Upgrade request, typically when connecting through an HTTP proxy over
+     *            clear HTTP
+     * @param generateOriginHeader
+     *            Allows to generate the `Origin`|`Sec-WebSocket-Origin` header value for handshake request
+     *            according to the given webSocketURL
+     */
+    public static WebSocketClientHandshaker newHandshaker(
+            URI webSocketURL, WebSocketVersion version, String subprotocol,
+            boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
+            boolean performMasking, boolean allowMaskMismatch, long forceCloseTimeoutMillis,
+            boolean absoluteUpgradeUrl, boolean generateOriginHeader) {
+        if (version == V13) {
+            return new WebSocketClientHandshaker13(
+                    webSocketURL, V13, subprotocol, allowExtensions, customHeaders,
+                    maxFramePayloadLength, performMasking, allowMaskMismatch, forceCloseTimeoutMillis,
+                    absoluteUpgradeUrl, generateOriginHeader);
+        }
+        if (version == V08) {
+            return new WebSocketClientHandshaker08(
+                    webSocketURL, V08, subprotocol, allowExtensions, customHeaders,
+                    maxFramePayloadLength, performMasking, allowMaskMismatch, forceCloseTimeoutMillis,
+                    absoluteUpgradeUrl, generateOriginHeader);
+        }
+        if (version == V07) {
+            return new WebSocketClientHandshaker07(
+                    webSocketURL, V07, subprotocol, allowExtensions, customHeaders,
+                    maxFramePayloadLength, performMasking, allowMaskMismatch, forceCloseTimeoutMillis,
+                    absoluteUpgradeUrl, generateOriginHeader);
+        }
+        if (version == V00) {
+            return new WebSocketClientHandshaker00(
+                    webSocketURL, V00, subprotocol, customHeaders,
+                    maxFramePayloadLength, forceCloseTimeoutMillis, absoluteUpgradeUrl, generateOriginHeader);
+        }
+
+        throw new WebSocketClientHandshakeException("Protocol version " + version + " not supported.");
+    }
 }
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolConfig.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolConfig.java
index 0cfd6c39db8..6700c7818ad 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolConfig.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolConfig.java
@@ -34,6 +34,7 @@ public final class WebSocketClientProtocolConfig {
     static final boolean DEFAULT_ALLOW_MASK_MISMATCH = false;
     static final boolean DEFAULT_HANDLE_CLOSE_FRAMES = true;
     static final boolean DEFAULT_DROP_PONG_FRAMES = true;
+    static final boolean DEFAULT_GENERATE_ORIGIN_HEADER = true;
 
     private final URI webSocketUri;
     private final String subprotocol;
@@ -49,6 +50,7 @@ public final class WebSocketClientProtocolConfig {
     private final long handshakeTimeoutMillis;
     private final long forceCloseTimeoutMillis;
     private final boolean absoluteUpgradeUrl;
+    private final boolean generateOriginHeader;
 
     private WebSocketClientProtocolConfig(
         URI webSocketUri,
@@ -64,7 +66,8 @@ private WebSocketClientProtocolConfig(
         boolean dropPongFrames,
         long handshakeTimeoutMillis,
         long forceCloseTimeoutMillis,
-        boolean absoluteUpgradeUrl
+        boolean absoluteUpgradeUrl,
+        boolean generateOriginHeader
     ) {
         this.webSocketUri = webSocketUri;
         this.subprotocol = subprotocol;
@@ -80,6 +83,7 @@ private WebSocketClientProtocolConfig(
         this.dropPongFrames = dropPongFrames;
         this.handshakeTimeoutMillis = checkPositive(handshakeTimeoutMillis, "handshakeTimeoutMillis");
         this.absoluteUpgradeUrl = absoluteUpgradeUrl;
+        this.generateOriginHeader = generateOriginHeader;
     }
 
     public URI webSocketUri() {
@@ -138,24 +142,29 @@ public boolean absoluteUpgradeUrl() {
         return absoluteUpgradeUrl;
     }
 
+    public boolean generateOriginHeader() {
+        return generateOriginHeader;
+    }
+
     @Override
     public String toString() {
         return "WebSocketClientProtocolConfig" +
-            " {webSocketUri=" + webSocketUri +
-            ", subprotocol=" + subprotocol +
-            ", version=" + version +
-            ", allowExtensions=" + allowExtensions +
-            ", customHeaders=" + customHeaders +
-            ", maxFramePayloadLength=" + maxFramePayloadLength +
-            ", performMasking=" + performMasking +
-            ", allowMaskMismatch=" + allowMaskMismatch +
-            ", handleCloseFrames=" + handleCloseFrames +
-            ", sendCloseFrame=" + sendCloseFrame +
-            ", dropPongFrames=" + dropPongFrames +
-            ", handshakeTimeoutMillis=" + handshakeTimeoutMillis +
-            ", forceCloseTimeoutMillis=" + forceCloseTimeoutMillis +
-            ", absoluteUpgradeUrl=" + absoluteUpgradeUrl +
-            "}";
+               " {webSocketUri=" + webSocketUri +
+               ", subprotocol=" + subprotocol +
+               ", version=" + version +
+               ", allowExtensions=" + allowExtensions +
+               ", customHeaders=" + customHeaders +
+               ", maxFramePayloadLength=" + maxFramePayloadLength +
+               ", performMasking=" + performMasking +
+               ", allowMaskMismatch=" + allowMaskMismatch +
+               ", handleCloseFrames=" + handleCloseFrames +
+               ", sendCloseFrame=" + sendCloseFrame +
+               ", dropPongFrames=" + dropPongFrames +
+               ", handshakeTimeoutMillis=" + handshakeTimeoutMillis +
+               ", forceCloseTimeoutMillis=" + forceCloseTimeoutMillis +
+               ", absoluteUpgradeUrl=" + absoluteUpgradeUrl +
+               ", generateOriginHeader=" + generateOriginHeader +
+               "}";
     }
 
     public Builder toBuilder() {
@@ -177,7 +186,8 @@ public static Builder newBuilder() {
                 DEFAULT_DROP_PONG_FRAMES,
                 DEFAULT_HANDSHAKE_TIMEOUT_MILLIS,
                 -1,
-                false);
+                false,
+                DEFAULT_GENERATE_ORIGIN_HEADER);
     }
 
     public static final class Builder {
@@ -195,6 +205,7 @@ public static final class Builder {
         private long handshakeTimeoutMillis;
         private long forceCloseTimeoutMillis;
         private boolean absoluteUpgradeUrl;
+        private boolean generateOriginHeader;
 
         private Builder(WebSocketClientProtocolConfig clientConfig) {
             this(ObjectUtil.checkNotNull(clientConfig, "clientConfig").webSocketUri(),
@@ -210,7 +221,8 @@ private Builder(WebSocketClientProtocolConfig clientConfig) {
                  clientConfig.dropPongFrames(),
                  clientConfig.handshakeTimeoutMillis(),
                  clientConfig.forceCloseTimeoutMillis(),
-                 clientConfig.absoluteUpgradeUrl());
+                 clientConfig.absoluteUpgradeUrl(),
+                 clientConfig.generateOriginHeader());
         }
 
         private Builder(URI webSocketUri,
@@ -226,7 +238,8 @@ private Builder(URI webSocketUri,
                         boolean dropPongFrames,
                         long handshakeTimeoutMillis,
                         long forceCloseTimeoutMillis,
-                        boolean absoluteUpgradeUrl) {
+                        boolean absoluteUpgradeUrl,
+                        boolean generateOriginHeader) {
             this.webSocketUri = webSocketUri;
             this.subprotocol = subprotocol;
             this.version = version;
@@ -241,6 +254,7 @@ private Builder(URI webSocketUri,
             this.handshakeTimeoutMillis = handshakeTimeoutMillis;
             this.forceCloseTimeoutMillis = forceCloseTimeoutMillis;
             this.absoluteUpgradeUrl = absoluteUpgradeUrl;
+            this.generateOriginHeader = generateOriginHeader;
         }
 
         /**
@@ -367,6 +381,16 @@ public Builder absoluteUpgradeUrl(boolean absoluteUpgradeUrl) {
             return this;
         }
 
+        /**
+         * Allows to generate the `Origin`|`Sec-WebSocket-Origin` header value for handshake request
+         * according the given webSocketURI. Usually it's not necessary and can be disabled,
+         * but for backward compatibility is set to {@code true} as default.
+         */
+        public Builder generateOriginHeader(boolean generateOriginHeader) {
+            this.generateOriginHeader = generateOriginHeader;
+            return this;
+        }
+
         /**
          * Build unmodifiable client protocol configuration.
          */
@@ -385,7 +409,8 @@ public WebSocketClientProtocolConfig build() {
                 dropPongFrames,
                 handshakeTimeoutMillis,
                 forceCloseTimeoutMillis,
-                absoluteUpgradeUrl
+                absoluteUpgradeUrl,
+                generateOriginHeader
             );
         }
     }
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java
index 257d3f025c9..2f8e262ca0a 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java
@@ -94,7 +94,8 @@ public WebSocketClientProtocolHandler(WebSocketClientProtocolConfig clientConfig
             clientConfig.performMasking(),
             clientConfig.allowMaskMismatch(),
             clientConfig.forceCloseTimeoutMillis(),
-            clientConfig.absoluteUpgradeUrl()
+            clientConfig.absoluteUpgradeUrl(),
+            clientConfig.generateOriginHeader()
         );
         this.clientConfig = clientConfig;
     }


Test patch:
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java
index 9d1606f3707..d84c9032c2a 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00Test.java
@@ -23,9 +23,9 @@
 public class WebSocketClientHandshaker00Test extends WebSocketClientHandshakerTest {
     @Override
     protected WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers,
-                                                      boolean absoluteUpgradeUrl) {
+                                                      boolean absoluteUpgradeUrl, boolean generateOriginHeader) {
         return new WebSocketClientHandshaker00(uri, WebSocketVersion.V00, subprotocol, headers,
-          1024, 10000, absoluteUpgradeUrl);
+          1024, 10000, absoluteUpgradeUrl, generateOriginHeader);
     }
 
     @Override
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java
index 692bc3bbe64..e3f04da85f2 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07Test.java
@@ -31,7 +31,7 @@ public class WebSocketClientHandshaker07Test extends WebSocketClientHandshakerTe
     public void testHostHeaderPreserved() {
         URI uri = URI.create("ws://localhost:9999");
         WebSocketClientHandshaker handshaker = newHandshaker(uri, null,
-                new DefaultHttpHeaders().set(HttpHeaderNames.HOST, "test.netty.io"), false);
+                new DefaultHttpHeaders().set(HttpHeaderNames.HOST, "test.netty.io"), false, true);
 
         FullHttpRequest request = handshaker.newHandshakeRequest();
         try {
@@ -44,10 +44,10 @@ public void testHostHeaderPreserved() {
 
     @Override
     protected WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers,
-                                                      boolean absoluteUpgradeUrl) {
+                                                      boolean absoluteUpgradeUrl, boolean generateOriginHeader) {
         return new WebSocketClientHandshaker07(uri, WebSocketVersion.V07, subprotocol, false, headers,
           1024, true, false, 10000,
-          absoluteUpgradeUrl);
+          absoluteUpgradeUrl, generateOriginHeader);
     }
 
     @Override
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java
index 34c5fb76d8b..aa8e11dd50a 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08Test.java
@@ -22,9 +22,9 @@
 public class WebSocketClientHandshaker08Test extends WebSocketClientHandshaker07Test {
     @Override
     protected WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers,
-                                                      boolean absoluteUpgradeUrl) {
+                                                      boolean absoluteUpgradeUrl, boolean generateOriginHeader) {
         return new WebSocketClientHandshaker08(uri, WebSocketVersion.V08, subprotocol, false, headers,
           1024, true, true, 10000,
-          absoluteUpgradeUrl);
+          absoluteUpgradeUrl, generateOriginHeader);
     }
 }
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java
index 2371caed598..cd82f214546 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13Test.java
@@ -24,10 +24,10 @@ public class WebSocketClientHandshaker13Test extends WebSocketClientHandshaker07
 
     @Override
     protected WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers,
-                                                      boolean absoluteUpgradeUrl) {
+                                                      boolean absoluteUpgradeUrl, boolean generateOriginHeader) {
         return new WebSocketClientHandshaker13(uri, WebSocketVersion.V13, subprotocol, false, headers,
           1024, true, true, 10000,
-          absoluteUpgradeUrl);
+          absoluteUpgradeUrl, generateOriginHeader);
     }
 
     @Override
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java
index 1be884e0b66..411e8ae9c2a 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshakerTest.java
@@ -51,6 +51,7 @@
 import static io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker13.WEBSOCKET_13_ACCEPT_GUID;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -59,10 +60,11 @@
 
 public abstract class WebSocketClientHandshakerTest {
     protected abstract WebSocketClientHandshaker newHandshaker(URI uri, String subprotocol, HttpHeaders headers,
-                                                               boolean absoluteUpgradeUrl);
+                                                               boolean absoluteUpgradeUrl,
+                                                               boolean generateOriginHeader);
 
     protected WebSocketClientHandshaker newHandshaker(URI uri) {
-        return newHandshaker(uri, null, null, false);
+        return newHandshaker(uri, null, null, false, true);
     }
 
     protected abstract CharSequence getOriginHeaderName();
@@ -183,7 +185,7 @@ public void originHeaderWithoutScheme() {
     public void testSetOriginFromCustomHeaders() {
         HttpHeaders customHeaders = new DefaultHttpHeaders().set(getOriginHeaderName(), "http://example.com");
         WebSocketClientHandshaker handshaker = newHandshaker(URI.create("ws://server.example.com/chat"), null,
-                                                             customHeaders, false);
+                                                             customHeaders, false, true);
         FullHttpRequest request = handshaker.newHandshakeRequest();
         try {
             assertEquals("http://example.com", request.headers().get(getOriginHeaderName()));
@@ -192,6 +194,50 @@ public void testSetOriginFromCustomHeaders() {
         }
     }
 
+    @Test
+    public void testOriginHeaderIsAbsentWhenGeneratingDisable() {
+        URI uri = URI.create("http://example.com/ws");
+        WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, false, false);
+        FullHttpRequest request = handshaker.newHandshakeRequest();
+        try {
+            assertFalse(request.headers().contains(getOriginHeaderName()));
+            assertEquals("/ws", request.uri());
+        } finally {
+            request.release();
+        }
+    }
+
+    @Test
+    public void testInvalidHostWhenIncorrectWebSocketURI() {
+        URI uri = URI.create("/ws");
+        EmbeddedChannel channel = new EmbeddedChannel(new HttpClientCodec());
+        final WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, false, true);
+        final ChannelFuture handshakeFuture = handshaker.handshake(channel);
+
+        assertFalse(handshakeFuture.isSuccess());
+        assertInstanceOf(IllegalArgumentException.class, handshakeFuture.cause());
+        assertEquals("Cannot generate the 'host' header value, webSocketURI should contain host" +
+                     " or passed through customHeaders", handshakeFuture.cause().getMessage());
+        assertFalse(channel.finish());
+    }
+
+    @Test
+    public void testInvalidOriginWhenIncorrectWebSocketURI() {
+        URI uri = URI.create("/ws");
+        EmbeddedChannel channel = new EmbeddedChannel(new HttpClientCodec());
+        HttpHeaders headers = new DefaultHttpHeaders();
+        headers.set(HttpHeaderNames.HOST, "localhost:80");
+        final WebSocketClientHandshaker handshaker = newHandshaker(uri, null, headers, false, true);
+        final ChannelFuture handshakeFuture = handshaker.handshake(channel);
+
+        assertFalse(handshakeFuture.isSuccess());
+        assertInstanceOf(IllegalArgumentException.class, handshakeFuture.cause());
+        assertEquals("Cannot generate the '" + getOriginHeaderName() + "' header value," +
+                     " webSocketURI should contain host or disable generateOriginHeader" +
+                     " or pass value through customHeaders", handshakeFuture.cause().getMessage());
+        assertFalse(channel.finish());
+    }
+
     private void testHostHeader(String uri, String expected) {
         testHeaderDefaultHttp(uri, HttpHeaderNames.HOST, expected);
     }
@@ -262,7 +308,7 @@ public void testUpgradeUrlWithoutPathWithQuery() {
     @Test
     public void testAbsoluteUpgradeUrlWithQuery() {
         URI uri = URI.create("ws://localhost:9999/path%20with%20ws?a=b%20c");
-        WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, true);
+        WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, true, true);
         FullHttpRequest request = handshaker.newHandshakeRequest();
         try {
             assertEquals("ws://localhost:9999/path%20with%20ws?a=b%20c", request.uri());
@@ -392,7 +438,7 @@ public void testDuplicateWebsocketHandshakeHeaders() {
         inputHeaders.add(getProtocolHeaderName(), bogusSubProtocol);
 
         String realSubProtocol = "realSubProtocol";
-        WebSocketClientHandshaker handshaker = newHandshaker(uri, realSubProtocol, inputHeaders, false);
+        WebSocketClientHandshaker handshaker = newHandshaker(uri, realSubProtocol, inputHeaders, false, true);
         FullHttpRequest request = handshaker.newHandshakeRequest();
         HttpHeaders outputHeaders = request.headers();
 
@@ -412,7 +458,7 @@ public void testDuplicateWebsocketHandshakeHeaders() {
     @Test
     public void testWebSocketClientHandshakeException() {
         URI uri = URI.create("ws://localhost:9999/exception");
-        WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, false);
+        WebSocketClientHandshaker handshaker = newHandshaker(uri, null, null, false, true);
         FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED);
         response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE, "realm = access token required");