001/** 002 * 003 * Copyright 2020 Aditya Borikar, 2020-2021 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.websocket.okhttp; 018 019import java.net.URI; 020import java.util.logging.Level; 021import java.util.logging.Logger; 022 023import javax.net.ssl.SSLSession; 024 025import org.jivesoftware.smack.SmackFuture; 026import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; 027import org.jivesoftware.smack.websocket.WebSocketException; 028import org.jivesoftware.smack.websocket.impl.AbstractWebSocket; 029import org.jivesoftware.smack.websocket.rce.WebSocketRemoteConnectionEndpoint; 030 031import okhttp3.OkHttpClient; 032import okhttp3.Request; 033import okhttp3.Response; 034import okhttp3.WebSocket; 035import okhttp3.WebSocketListener; 036 037public final class OkHttpWebSocket extends AbstractWebSocket { 038 039 private static final Logger LOGGER = Logger.getLogger(OkHttpWebSocket.class.getName()); 040 041 private static final OkHttpClient okHttpClient = new OkHttpClient(); 042 043 // This is a potential candidate to be placed into AbstractWebSocket, but I keep it here until smack-websocket-java11 044 // arrives. 045 private final SmackFuture.InternalSmackFuture<AbstractWebSocket, Exception> future = new SmackFuture.InternalSmackFuture<>(); 046 047 private final LoggingInterceptor interceptor; 048 049 private final WebSocket okHttpWebSocket; 050 051 public OkHttpWebSocket(WebSocketRemoteConnectionEndpoint endpoint, 052 ModularXmppClientToServerConnectionInternal connectionInternal) { 053 super(endpoint, connectionInternal); 054 055 if (connectionInternal.smackDebugger != null) { 056 interceptor = new LoggingInterceptor(connectionInternal.smackDebugger); 057 } else { 058 interceptor = null; 059 } 060 061 final URI uri = endpoint.getUri(); 062 final String url = uri.toString(); 063 064 Request request = new Request.Builder() 065 .url(url) 066 .header("Sec-WebSocket-Protocol", "xmpp") 067 .build(); 068 069 okHttpWebSocket = okHttpClient.newWebSocket(request, listener); 070 } 071 072 private final WebSocketListener listener = new WebSocketListener() { 073 074 @Override 075 public void onOpen(WebSocket webSocket, Response response) { 076 LOGGER.log(Level.FINER, "OkHttp invoked onOpen() for {0}. Response: {1}", 077 new Object[] { webSocket, response }); 078 079 if (interceptor != null) { 080 interceptor.interceptOpenResponse(response); 081 } 082 083 future.setResult(OkHttpWebSocket.this); 084 } 085 086 @Override 087 public void onMessage(WebSocket webSocket, String text) { 088 if (interceptor != null) { 089 interceptor.interceptReceivedText(text); 090 } 091 092 onIncomingWebSocketElement(text); 093 } 094 095 @Override 096 public void onFailure(WebSocket webSocket, Throwable throwable, Response response) { 097 LOGGER.log(Level.FINER, "OkHttp invoked onFailure() for " + webSocket + ". Response: " + response, throwable); 098 WebSocketException websocketException = new WebSocketException(throwable); 099 100 // If we are already connected, then we need to notify the connection that it got tear down. Otherwise we 101 // need to notify the thread calling connect() that the connection failed. 102 if (future.wasSuccessful()) { 103 connectionInternal.notifyConnectionError(websocketException); 104 } else { 105 future.setException(websocketException); 106 } 107 } 108 109 @Override 110 public void onClosing(WebSocket webSocket, int code, String reason) { 111 LOGGER.log(Level.FINER, "OkHttp invoked onClosing() for " + webSocket + ". Code: " + code + ". Reason: " + reason); 112 } 113 114 @Override 115 public void onClosed(WebSocket webSocket, int code, String reason) { 116 LOGGER.log(Level.FINER, "OkHttp invoked onClosed() for " + webSocket + ". Code: " + code + ". Reason: " + reason); 117 } 118 119 }; 120 121 @Override 122 public SmackFuture<AbstractWebSocket, Exception> getFuture() { 123 return future; 124 } 125 126 @Override 127 public void send(String element) { 128 if (interceptor != null) { 129 interceptor.interceptSentText(element); 130 } 131 okHttpWebSocket.send(element); 132 } 133 134 @Override 135 public void disconnect(int code, String message) { 136 LOGGER.log(Level.INFO, "WebSocket closing with code: " + code + " and message: " + message); 137 okHttpWebSocket.close(code, message); 138 } 139 140 @Override 141 public boolean isConnectionSecure() { 142 return endpoint.isSecureEndpoint(); 143 } 144 145 @Override 146 public boolean isConnected() { 147 // TODO: Do we need this method at all if we create an AbstractWebSocket object for every endpoint? 148 return true; 149 } 150 151 @Override 152 public SSLSession getSSLSession() { 153 // TODO: What shall we do about this method, as it appears that OkHttp does not provide access to the used SSLSession? 154 return null; 155 } 156}