001/*
002 * Copyright (C) 2015-2018 Neo Visionaries Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package NeoVisionaries.WebSockets;
017
018
019import java.io.IOException;
020import java.net.Socket;
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.net.URL;
024import javax.net.SocketFactory;
025import javax.net.ssl.SSLContext;
026import javax.net.ssl.SSLSocketFactory;
027
028
029/**
030 * Factory to create {@link WebSocket} instances.
031 * 
032 * <EMBED CLASS='external-html' DATA-FILE-ID=LICENSE>
033 */
034public class WebSocketFactory
035{
036    private final SocketFactorySettings mSocketFactorySettings;
037    private final ProxySettings mProxySettings;
038    private int mConnectionTimeout;
039    private int mSocketTimeout;
040    private DualStackMode mDualStackMode = DualStackMode.BOTH;
041    private int mDualStackFallbackDelay = 250;
042    private boolean mVerifyHostname = true;
043    private String[] mServerNames;
044
045
046    /**
047     * Constructor.
048     */
049    public WebSocketFactory()
050    {
051        mSocketFactorySettings = new SocketFactorySettings();
052        mProxySettings         = new ProxySettings(this);
053    }
054
055
056    /**
057     * Copy constructor.
058     *
059     * @param other
060     *         A {@code WebSocketFactory} instance to copy.
061     *
062     * @throws IllegalArgumentException
063     *         If the given {@code WebSocketFactory} instance is null.
064     *
065     * @since 2.10
066     */
067    public WebSocketFactory(WebSocketFactory other)
068    {
069        if (other == null)
070        {
071            throw new IllegalArgumentException("The given WebSocketFactory is null");
072        }
073
074        mSocketFactorySettings  = new SocketFactorySettings(other.mSocketFactorySettings);
075        mProxySettings          = new ProxySettings(this, other.mProxySettings);
076        mConnectionTimeout      = other.mConnectionTimeout;
077        mSocketTimeout          = other.mSocketTimeout;
078        mDualStackMode          = other.mDualStackMode;
079        mDualStackFallbackDelay = other.mDualStackFallbackDelay;
080        mVerifyHostname         = other.mVerifyHostname;
081
082        if (other.mServerNames != null)
083        {
084            mServerNames = new String[other.mServerNames.length];
085            System.arraycopy(other.mServerNames, 0, mServerNames, 0, mServerNames.length);
086        }
087    }
088
089
090    /**
091     * Get the socket factory that has been set by {@link
092     * #setSocketFactory(SocketFactory)}.
093     *
094     * @return
095     *         The socket factory.
096     */
097    public SocketFactory getSocketFactory()
098    {
099        return mSocketFactorySettings.getSocketFactory();
100    }
101
102
103    /**
104     * Set a socket factory.
105     * See {@link #createSocket(URI)} for details.
106     *
107     * @param factory
108     *         A socket factory.
109     *
110     * @return
111     *         {@code this} instance.
112     */
113    public WebSocketFactory setSocketFactory(SocketFactory factory)
114    {
115        mSocketFactorySettings.setSocketFactory(factory);
116
117        return this;
118    }
119
120
121    /**
122     * Get the SSL socket factory that has been set by {@link
123     * #setSSLSocketFactory(SSLSocketFactory)}.
124     *
125     * @return
126     *         The SSL socket factory.
127     */
128    public SSLSocketFactory getSSLSocketFactory()
129    {
130        return mSocketFactorySettings.getSSLSocketFactory();
131    }
132
133
134    /**
135     * Set an SSL socket factory.
136     * See {@link #createSocket(URI)} for details.
137     *
138     * @param factory
139     *         An SSL socket factory.
140     *
141     * @return
142     *         {@code this} instance.
143     */
144    public WebSocketFactory setSSLSocketFactory(SSLSocketFactory factory)
145    {
146        mSocketFactorySettings.setSSLSocketFactory(factory);
147
148        return this;
149    }
150
151
152    /**
153     * Get the SSL context that has been set by {@link #setSSLContext(SSLContext)}.
154     *
155     * @return
156     *         The SSL context.
157     */
158    public SSLContext getSSLContext()
159    {
160        return mSocketFactorySettings.getSSLContext();
161    }
162
163
164    /**
165     * Set an SSL context to get a socket factory.
166     * See {@link #createSocket(URI)} for details.
167     *
168     * @param context
169     *         An SSL context.
170     *
171     * @return
172     *         {@code this} instance.
173     */
174    public WebSocketFactory setSSLContext(SSLContext context)
175    {
176        mSocketFactorySettings.setSSLContext(context);
177
178        return this;
179    }
180
181
182    /**
183     * Get the proxy settings.
184     *
185     * @return
186     *         The proxy settings.
187     *
188     * @since 1.3
189     *
190     * @see ProxySettings
191     */
192    public ProxySettings getProxySettings()
193    {
194        return mProxySettings;
195    }
196
197
198    /**
199     * Get the timeout value in milliseconds for socket connection.
200     * The default value is 0 and it means an infinite timeout.
201     *
202     * <p>
203     * When a {@code createSocket} method which does not have {@code
204     * timeout} argument is called, the value returned by this method
205     * is used as a timeout value for socket connection.
206     * </p>
207     *
208     * @return
209     *         The connection timeout value in milliseconds.
210     *
211     * @since 1.10
212     */
213    public int getConnectionTimeout()
214    {
215        return mConnectionTimeout;
216    }
217
218
219    /**
220     * Set the timeout value in milliseconds for socket connection.
221     * A timeout of zero is interpreted as an infinite timeout.
222     *
223     * @param timeout
224     *         The connection timeout value in milliseconds.
225     *
226     * @return
227     *         {@code this} object.
228     *
229     * @throws IllegalArgumentException
230     *         The given timeout value is negative.
231     *
232     * @since 1.10
233     */
234    public WebSocketFactory setConnectionTimeout(int timeout)
235    {
236        if (timeout < 0)
237        {
238            throw new IllegalArgumentException("timeout value cannot be negative.");
239        }
240
241        mConnectionTimeout = timeout;
242
243        return this;
244    }
245
246
247    /**
248     * Get the timeout value in milliseconds for socket read and write operations.
249     * The default value is 0 and it means an infinite timeout.
250     *
251     * <p>
252     * This can be changed later with {@code getSocket().setSoTimeout(int)}.
253     * </p>
254     *
255     * @return
256     *         The socket timeout value in milliseconds.
257     *
258     * @see   Socket#setSoTimeout(int)
259     *
260     * @since 2.14
261     */
262    public int getSocketTimeout()
263    {
264        return mSocketTimeout;
265    }
266
267
268    /**
269     * Set the timeout value in milliseconds for socket read and write operations.
270     * A timeout of zero is interpreted as an infinite timeout.
271     *
272     * <p>
273     * This can be changed later with {@code getSocket().setSoTimeout(int)}.
274     * </p>
275     *
276     * @param timeout
277     *         The socket timeout value in milliseconds.
278     *
279     * @return
280     *         {@code this} object.
281     *
282     * @throws IllegalArgumentException
283     *         The given timeout value is negative.
284     *
285     * @see   Socket#setSoTimeout(int)
286     *
287     * @since 2.14
288     */
289    public WebSocketFactory setSocketTimeout(int timeout)
290    {
291        if (timeout < 0)
292        {
293            throw new IllegalArgumentException("timeout value cannot be negative.");
294        }
295
296        mSocketTimeout = timeout;
297
298        return this;
299    }
300
301
302    /**
303     * Get the dual stack mode that will be applied when establishing a socket
304     * connection. The default value is {@link DualStackMode#BOTH}.
305     *
306     * <p>
307     * A hostname may resolve to an arbitrary amount of IPv4 and IPv6 addresses.
308     * This controls which IP address families will be used when establishing
309     * a connection. Note that IPv6 will be preferred, if activated.
310     * </p>
311     *
312     * @return
313     *         The dual stack mode.
314     */
315    public DualStackMode getDualStackMode()
316    {
317        return mDualStackMode;
318    }
319
320
321    /**
322     * Set the dual stack mode that will be applied when establishing a socket
323     * connection.
324     *
325     * @param mode
326     *         The dual stack mode to be applied.
327     *
328     * @return
329     *         {@code this} object.
330     */
331    public WebSocketFactory setDualStackMode(DualStackMode mode)
332    {
333        mDualStackMode = mode;
334
335        return this;
336    }
337
338
339    /**
340     * Get the dual stack fallback delay in milliseconds that will be applied
341     * when establishing a socket connection.
342     *
343     * <p>
344     * A hostname may resolve to an arbitrary amount of IPv4 and IPv6 addresses.
345     * This controls the maximum amount of time that may pass between attempts
346     * to establish a socket connection to an IP addresses before trying the
347     * next one. Note that the previous attempt will not be aborted. The
348     * connections will race until one has been established.
349     * </p>
350     *
351     * @return
352     *         The dual stack fallback delay in milliseconds.
353     */
354    public int getDualStackFallbackDelay()
355    {
356        return mDualStackFallbackDelay;
357    }
358
359
360    /**
361     * Set the dual stack fallback delay in milliseconds that will be applied
362     * when establishing a socket connection.
363     *
364     * @param delay
365     *         The dual stack fallback delay in milliseconds.
366     *
367     * @return
368     *         {@code this} object.
369     */
370    public WebSocketFactory setDualStackFallbackDelay(int delay)
371    {
372
373        if (delay < 0)
374        {
375            throw new IllegalArgumentException("delay value cannot be negative.");
376        }
377
378        mDualStackFallbackDelay = delay;
379
380        return this;
381    }
382
383
384    /**
385     * Get the flag which indicates whether the hostname in the
386     * server's certificate should be verified or not. The default
387     * value is {@code true}. See the description of {@link
388     * #setVerifyHostname(boolean)} to understand what this boolean
389     * flag means.
390     *
391     * @return
392     *         {@code true} if hostname verification is enabled.
393     *
394     * @since 2.3
395     */
396    public boolean getVerifyHostname()
397    {
398        return mVerifyHostname;
399    }
400
401
402    /**
403     * Set the flag which indicates whether the hostname in the
404     * server's certificate should be verified or not. The default
405     * value is {@code true}.
406     *
407     * <p>
408     * Manual hostname verification has been enabled since the version
409     * 2.1. Because the verification is executed manually after {@code
410     * Socket.}{@link java.net.Socket#connect(java.net.SocketAddress, int)
411     * connect(SocketAddress, int)}
412     * succeeds, the hostname verification is always executed even if
413     * you has passed an {@link SSLContext} which naively accepts any
414     * server certificate (e.g. <code><a href=
415     * "https://gist.github.com/TakahikoKawasaki/d07de2218b4b81bf65ac"
416     * >NaiveSSLContext</a></code>). However, this behavior is not
417     * desirable in some cases and you may want to disable the hostname
418     * verification. This setter method exists for the purpose and you
419     * can disable hostname verification by passing {@code false} to
420     * this method.
421     * </p>
422     *
423     * @param verifyHostname
424     *         {@code true} to enable hostname verification.
425     *         {@code false} to disable hostname verification.
426     *
427     * @return
428     *         {@code this} object.
429     *
430     * @since 2.3
431     */
432    public WebSocketFactory setVerifyHostname(boolean verifyHostname)
433    {
434        mVerifyHostname = verifyHostname;
435
436        return this;
437    }
438
439
440    /**
441     * Get server names for SNI (Server Name Indication).
442     *
443     * @return
444     *         List of host names.
445     *
446     * @since 2.4
447     */
448    public String[] getServerNames()
449    {
450        return mServerNames;
451    }
452
453
454    /**
455     * Set server names for SNI (Server Name Indication).
456     *
457     * If {@code setServerNames(List<SNIServerName>)} method of
458     * {@link javax.net.ssl.SSLParameters SSLParameters} class is available
459     * in the underlying system, the method is called to set up server names
460     * for SNI (Server Name Indication).
461     *
462     * @param serverNames
463     *         List of host names.
464     *
465     * @return
466     *         {@code this} object.
467     *
468     * @since 2.4
469     */
470    public WebSocketFactory setServerNames(String[] serverNames)
471    {
472        mServerNames = serverNames;
473
474        return this;
475    }
476
477
478    /**
479     * Set a server name for SNI (Server Name Indication).
480     *
481     * This method internally creates a String array of size 1 which
482     * contains the given {@code serverName} and calls {@link
483     * #setServerNames(String[])}.
484     *
485     * @param serverName
486     *         A host name.
487     *
488     * @return
489     *         {@code this} object.
490     *
491     * @since 2.4
492     */
493    public WebSocketFactory setServerName(String serverName)
494    {
495        return setServerNames(new String[] { serverName });
496    }
497
498
499    /**
500     * Create a WebSocket.
501     *
502     * <p>
503     * This method is an alias of {@link #createSocket(String, int)
504     * createSocket}{@code (uri, }{@link #getConnectionTimeout()}{@code )}.
505     * </p>
506     *
507     * @param uri
508     *         The URI of the WebSocket endpoint on the server side.
509     *
510     * @return
511     *         A WebSocket.
512     *
513     * @throws IllegalArgumentException
514     *         The given URI is {@code null} or violates RFC 2396.
515     *
516     * @throws IOException
517     *         Failed to create a socket. Or, HTTP proxy handshake or SSL
518     *         handshake failed.
519     */
520    public WebSocket createSocket(String uri) throws IOException
521    {
522        return createSocket(uri, getConnectionTimeout());
523    }
524
525
526    /**
527     * Create a WebSocket.
528     *
529     * <p>
530     * This method is an alias of {@link #createSocket(URI, int) createSocket}{@code
531     * (}{@link URI#create(String) URI.create}{@code (uri), timeout)}.
532     * </p>
533     *
534     * @param uri
535     *         The URI of the WebSocket endpoint on the server side.
536     *
537     * @param timeout
538     *         The timeout value in milliseconds for socket connection.
539     *         A timeout of zero is interpreted as an infinite timeout.
540     *
541     * @return
542     *         A WebSocket.
543     *
544     * @throws IllegalArgumentException
545     *         The given URI is {@code null} or violates RFC 2396, or
546     *         the given timeout value is negative.
547     *
548     * @throws IOException
549     *         Failed to create a socket. Or, HTTP proxy handshake or SSL
550     *         handshake failed.
551     *
552     * @since 1.10
553     */
554    public WebSocket createSocket(String uri, int timeout) throws IOException
555    {
556        if (uri == null)
557        {
558            throw new IllegalArgumentException("The given URI is null.");
559        }
560
561        if (timeout < 0)
562        {
563            throw new IllegalArgumentException("The given timeout value is negative.");
564        }
565
566        return createSocket(URI.create(uri), timeout);
567    }
568
569
570    /**
571     * Create a WebSocket.
572     *
573     * <p>
574     * This method is an alias of {@link #createSocket(URL, int) createSocket}{@code
575     * (url, }{@link #getConnectionTimeout()}{@code )}.
576     * </p>
577     *
578     * @param url
579     *         The URL of the WebSocket endpoint on the server side.
580     *
581     * @return
582     *         A WebSocket.
583     *
584     * @throws IllegalArgumentException
585     *         The given URL is {@code null} or failed to be converted into a URI.
586     *
587     * @throws IOException
588     *         Failed to create a socket. Or, HTTP proxy handshake or SSL
589     *         handshake failed.
590     */
591    public WebSocket createSocket(URL url) throws IOException
592    {
593        return createSocket(url, getConnectionTimeout());
594    }
595
596
597    /**
598     * Create a WebSocket.
599     *
600     * <p>
601     * This method is an alias of {@link #createSocket(URI, int) createSocket}{@code
602     * (url.}{@link URL#toURI() toURI()}{@code , timeout)}.
603     * </p>
604     *
605     * @param url
606     *         The URL of the WebSocket endpoint on the server side.
607     *
608     * @param timeout
609     *         The timeout value in milliseconds for socket connection.
610     *
611     * @return
612     *         A WebSocket.
613     *
614     * @throws IllegalArgumentException
615     *         The given URL is {@code null} or failed to be converted into a URI,
616     *         or the given timeout value is negative.
617     *
618     * @throws IOException
619     *         Failed to create a socket. Or, HTTP proxy handshake or SSL
620     *         handshake failed.
621     *
622     * @since 1.10
623     */
624    public WebSocket createSocket(URL url, int timeout) throws IOException
625    {
626        if (url == null)
627        {
628            throw new IllegalArgumentException("The given URL is null.");
629        }
630
631        if (timeout < 0)
632        {
633            throw new IllegalArgumentException("The given timeout value is negative.");
634        }
635
636        try
637        {
638            return createSocket(url.toURI(), timeout);
639        }
640        catch (URISyntaxException e)
641        {
642            throw new IllegalArgumentException("Failed to convert the given URL into a URI.");
643        }
644    }
645
646
647    /**
648     * Create a WebSocket. This method is an alias of {@link #createSocket(URI, int)
649     * createSocket}{@code (uri, }{@link #getConnectionTimeout()}{@code )}.
650     *
651     * <p>
652     * A socket factory (= a {@link SocketFactory} instance) to create a raw
653     * socket (= a {@link Socket} instance) is determined as described below.
654     * </p>
655     *
656     * <ol>
657     * <li>
658     *   If the scheme of the URI is either {@code wss} or {@code https},
659     *   <ol type="i">
660     *     <li>
661     *       If an {@link SSLContext} instance has been set by {@link
662     *       #setSSLContext(SSLContext)}, the value returned from {@link
663     *       SSLContext#getSocketFactory()} method of the instance is used.
664     *     </li>
665     *     <li>
666     *       Otherwise, if an {@link SSLSocketFactory} instance has been
667     *       set by {@link #setSSLSocketFactory(SSLSocketFactory)}, the
668     *       instance is used.
669     *     </li>
670     *     <li>
671     *       Otherwise, the value returned from {@link SSLSocketFactory#getDefault()}
672     *       is used.
673     *     </li>
674     *   </ol>
675     * </li>
676     * <li>
677     *   Otherwise (= the scheme of the URI is either {@code ws} or {@code http}),
678     *   <ol type="i">
679     *     <li>
680     *       If a {@link SocketFactory} instance has been set by {@link
681     *       #setSocketFactory(SocketFactory)}, the instance is used.
682     *     </li>
683     *     <li>
684     *       Otherwise, the value returned from {@link SocketFactory#getDefault()}
685     *       is used.
686     *     </li>
687     *   </ol>
688     * </li>
689     * </ol>
690     *
691     * @param uri
692     *         The URI of the WebSocket endpoint on the server side.
693     *         The scheme part of the URI must be one of {@code ws},
694     *         {@code wss}, {@code http} and {@code https}
695     *         (case-insensitive).
696     *
697     * @return
698     *         A WebSocket.
699     *
700     * @throws IllegalArgumentException
701     *         The given URI is {@code null} or violates RFC 2396.
702     *
703     * @throws IOException
704     *         Failed to create a socket.
705     */
706    public WebSocket createSocket(URI uri) throws IOException
707    {
708        return createSocket(uri, getConnectionTimeout());
709    }
710
711
712    /**
713     * Create a WebSocket.
714     *
715     * <p>
716     * A socket factory (= a {@link SocketFactory} instance) to create a raw
717     * socket (= a {@link Socket} instance) is determined as described below.
718     * </p>
719     *
720     * <ol>
721     * <li>
722     *   If the scheme of the URI is either {@code wss} or {@code https},
723     *   <ol type="i">
724     *     <li>
725     *       If an {@link SSLContext} instance has been set by {@link
726     *       #setSSLContext(SSLContext)}, the value returned from {@link
727     *       SSLContext#getSocketFactory()} method of the instance is used.
728     *     </li>
729     *     <li>
730     *       Otherwise, if an {@link SSLSocketFactory} instance has been
731     *       set by {@link #setSSLSocketFactory(SSLSocketFactory)}, the
732     *       instance is used.
733     *     </li>
734     *     <li>
735     *       Otherwise, the value returned from {@link SSLSocketFactory#getDefault()}
736     *       is used.
737     *     </li>
738     *   </ol>
739     * </li>
740     * <li>
741     *   Otherwise (= the scheme of the URI is either {@code ws} or {@code http}),
742     *   <ol type="i">
743     *     <li>
744     *       If a {@link SocketFactory} instance has been set by {@link
745     *       #setSocketFactory(SocketFactory)}, the instance is used.
746     *     </li>
747     *     <li>
748     *       Otherwise, the value returned from {@link SocketFactory#getDefault()}
749     *       is used.
750     *     </li>
751     *   </ol>
752     * </li>
753     * </ol>
754     *
755     * @param uri
756     *         The URI of the WebSocket endpoint on the server side.
757     *         The scheme part of the URI must be one of {@code ws},
758     *         {@code wss}, {@code http} and {@code https}
759     *         (case-insensitive).
760     *
761     * @param timeout
762     *         The timeout value in milliseconds for socket connection.
763     *
764     * @return
765     *         A WebSocket.
766     *
767     * @throws IllegalArgumentException
768     *         The given URI is {@code null} or violates RFC 2396, or
769     *         the given timeout value is negative.
770     *
771     * @throws IOException
772     *         Failed to create a socket.
773     *
774     * @since 1.10
775     */
776    public WebSocket createSocket(URI uri, int timeout) throws IOException
777    {
778        if (uri == null)
779        {
780            throw new IllegalArgumentException("The given URI is null.");
781        }
782
783        if (timeout < 0)
784        {
785            throw new IllegalArgumentException("The given timeout value is negative.");
786        }
787
788        // Split the URI.
789        String scheme   = uri.getScheme();
790        String userInfo = uri.getUserInfo();
791        String host     = Misc.extractHost(uri);
792        int port        = uri.getPort();
793        String path     = uri.getRawPath();
794        String query    = uri.getRawQuery();
795
796        return createSocket(scheme, userInfo, host, port, path, query, timeout);
797    }
798
799
800    private WebSocket createSocket(
801        String scheme, String userInfo, String host, int port,
802        String path, String query, int timeout) throws IOException
803    {
804        // True if 'scheme' is 'wss' or 'https'.
805        boolean secure = isSecureConnectionRequired(scheme);
806
807        // Check if 'host' is specified.
808        if (host == null || host.length() == 0)
809        {
810            throw new IllegalArgumentException("The host part is empty.");
811        }
812
813        // Determine the path.
814        path = determinePath(path);
815
816        // Create a Socket instance and a connector to connect to the server.
817        SocketConnector connector = createRawSocket(host, port, secure, timeout);
818
819        // Create a WebSocket instance.
820        return createWebSocket(secure, userInfo, host, port, path, query, connector);
821    }
822
823
824    private static boolean isSecureConnectionRequired(String scheme)
825    {
826        if (scheme == null || scheme.length() == 0)
827        {
828            throw new IllegalArgumentException("The scheme part is empty.");
829        }
830
831        if ("wss".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme))
832        {
833            return true;
834        }
835
836        if ("ws".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme))
837        {
838            return false;
839        }
840
841        throw new IllegalArgumentException("Bad scheme: " + scheme);
842    }
843
844
845    private static String determinePath(String path)
846    {
847        if (path == null || path.length() == 0)
848        {
849            return "/";
850        }
851
852        if (path.startsWith("/"))
853        {
854            return path;
855        }
856        else
857        {
858            return "/" + path;
859        }
860    }
861
862
863    private SocketConnector createRawSocket(
864            String host, int port, boolean secure, int timeout) throws IOException
865    {
866        // Determine the port number. Especially, if 'port' is -1,
867        // it is converted to 80 or 443.
868        port = determinePort(port, secure);
869
870        // True if a proxy server should be used.
871        boolean proxied = (mProxySettings.getHost() != null);
872
873        // See "Figure 2 -- Proxy server traversal decision tree" at
874        // http://www.infoq.com/articles/Web-Sockets-Proxy-Servers
875
876        if (proxied)
877        {
878            // Create a connector to connect to the proxy server.
879            return createProxiedRawSocket(host, port, secure, timeout);
880        }
881        else
882        {
883            // Create a connector to connect to the WebSocket endpoint directly.
884            return createDirectRawSocket(host, port, secure, timeout);
885        }
886    }
887
888
889    private SocketConnector createProxiedRawSocket(
890            String host, int port, boolean secure, int timeout)
891    {
892        // Determine the port number of the proxy server.
893        // Especially, if getPort() returns -1, the value
894        // is converted to 80 or 443.
895        int proxyPort = determinePort(mProxySettings.getPort(), mProxySettings.isSecure());
896
897        // Select a socket factory.
898        SocketFactory factory = mProxySettings.selectSocketFactory();
899
900        // The address to connect to.
901        Address address = new Address(mProxySettings.getHost(), proxyPort);
902
903        // The delegatee for the handshake with the proxy.
904        ProxyHandshaker handshaker = new ProxyHandshaker(host, port, mProxySettings);
905
906        // SSLSocketFactory for SSL handshake with the WebSocket endpoint.
907        SSLSocketFactory sslSocketFactory = secure ?
908                (SSLSocketFactory)mSocketFactorySettings.selectSocketFactory(secure) : null;
909
910        // Create an instance that will execute the task to connect to the server later.
911        return new SocketConnector(
912                factory, address, timeout, mSocketTimeout, mProxySettings.getServerNames(), handshaker,
913                sslSocketFactory, host, port)
914                .setDualStackSettings(mDualStackMode, mDualStackFallbackDelay)
915                .setVerifyHostname(mVerifyHostname);
916    }
917
918
919    private SocketConnector createDirectRawSocket(String host, int port, boolean secure, int timeout)
920    {
921        // Select a socket factory.
922        SocketFactory factory = mSocketFactorySettings.selectSocketFactory(secure);
923
924        // The address to connect to.
925        Address address = new Address(host, port);
926
927        // Create an instance that will execute the task to connect to the server later.
928        return new SocketConnector(factory, address, timeout, mServerNames, mSocketTimeout)
929                .setDualStackSettings(mDualStackMode, mDualStackFallbackDelay)
930                .setVerifyHostname(mVerifyHostname);
931    }
932
933
934    private static int determinePort(int port, boolean secure)
935    {
936        if (0 <= port)
937        {
938            return port;
939        }
940
941        if (secure)
942        {
943            return 443;
944        }
945        else
946        {
947            return 80;
948        }
949    }
950
951
952    private WebSocket createWebSocket(
953        boolean secure, String userInfo, String host, int port,
954        String path, String query, SocketConnector connector)
955    {
956        // The value for "Host" HTTP header.
957        if (0 <= port)
958        {
959            host = host + ":" + port;
960        }
961
962        // The value for Request-URI of Request-Line.
963        if (query != null)
964        {
965            path = path + "?" + query;
966        }
967
968        return new WebSocket(this, secure, userInfo, host, path, connector);
969    }
970}