forked from square/okhttp
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request square#2935 from square/jwilson.1022.slack_client_…
…example A WebSockets sample that uses the Slack API.
- Loading branch information
Showing
10 changed files
with
548 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>com.squareup.okhttp3.sample</groupId> | ||
<artifactId>sample-parent</artifactId> | ||
<version>3.5.0-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>slack</artifactId> | ||
<name>Sample: Slack</name> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.squareup.okhttp3</groupId> | ||
<artifactId>mockwebserver</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.squareup.moshi</groupId> | ||
<artifactId>moshi</artifactId> | ||
</dependency> | ||
</dependencies> | ||
</project> |
42 changes: 42 additions & 0 deletions
42
samples/slack/src/main/java/okhttp3/slack/OAuthSession.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright (C) 2016 Square, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package okhttp3.slack; | ||
|
||
/** Authorization for an application to make Slack API calls on behalf of a user. */ | ||
@SuppressWarnings("checkstyle:membername") | ||
public final class OAuthSession { | ||
public final boolean ok; | ||
public final String access_token; | ||
public final String scope; | ||
public final String user_id; | ||
public final String team_name; | ||
public final String team_id; | ||
|
||
public OAuthSession( | ||
boolean ok, String accessToken, String scope, String userId, String teamName, String teamId) { | ||
this.ok = ok; | ||
this.access_token = accessToken; | ||
this.scope = scope; | ||
this.user_id = userId; | ||
this.team_name = teamName; | ||
this.team_id = teamId; | ||
} | ||
|
||
@Override public String toString() { | ||
return String.format("(ok=%s, access_token=%s, scope=%s, user_id=%s, team_name=%s, team_id=%s)", | ||
ok, access_token, scope, user_id, team_name, team_id); | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
samples/slack/src/main/java/okhttp3/slack/OAuthSessionFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/* | ||
* Copyright (C) 2016 Square, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package okhttp3.slack; | ||
|
||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.security.SecureRandom; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
import okhttp3.HttpUrl; | ||
import okhttp3.mockwebserver.Dispatcher; | ||
import okhttp3.mockwebserver.MockResponse; | ||
import okhttp3.mockwebserver.MockWebServer; | ||
import okhttp3.mockwebserver.RecordedRequest; | ||
import okio.ByteString; | ||
|
||
/** | ||
* Runs a MockWebServer on localhost and uses it as the backend to receive an OAuth session. | ||
* | ||
* <p>Clients should call {@link #start}, {@link #newAuthorizeUrl} and {@link #close} in that order. | ||
* Clients may request multiple sessions. | ||
*/ | ||
public final class OAuthSessionFactory extends Dispatcher implements Closeable { | ||
private final SecureRandom secureRandom = new SecureRandom(); | ||
|
||
private final SlackApi slackApi; | ||
private MockWebServer mockWebServer; | ||
|
||
/** Guarded by this. */ | ||
private Map<ByteString, Listener> listeners = new LinkedHashMap<>(); | ||
|
||
public OAuthSessionFactory(SlackApi slackApi) { | ||
this.slackApi = slackApi; | ||
} | ||
|
||
public void start() throws Exception { | ||
if (mockWebServer != null) throw new IllegalStateException(); | ||
|
||
mockWebServer = new MockWebServer(); | ||
mockWebServer.setDispatcher(this); | ||
mockWebServer.start(slackApi.port); | ||
} | ||
|
||
public HttpUrl newAuthorizeUrl(String scopes, String team, Listener listener) { | ||
if (mockWebServer == null) throw new IllegalStateException(); | ||
|
||
ByteString state = randomToken(); | ||
synchronized (this) { | ||
listeners.put(state, listener); | ||
} | ||
|
||
return slackApi.authorizeUrl(scopes, redirectUrl(), state, team); | ||
} | ||
|
||
private ByteString randomToken() { | ||
byte[] bytes = new byte[16]; | ||
secureRandom.nextBytes(bytes); | ||
return ByteString.of(bytes); | ||
} | ||
|
||
private HttpUrl redirectUrl() { | ||
return mockWebServer.url("/oauth/"); | ||
} | ||
|
||
/** When the browser hits the redirect URL, use the provied code to ask Slack for a session. */ | ||
@Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { | ||
HttpUrl requestUrl = mockWebServer.url(request.getPath()); | ||
String code = requestUrl.queryParameter("code"); | ||
String stateString = requestUrl.queryParameter("state"); | ||
ByteString state = stateString != null ? ByteString.decodeBase64(stateString) : null; | ||
|
||
Listener listener; | ||
synchronized (this) { | ||
listener = listeners.get(state); | ||
} | ||
|
||
if (code == null || listener == null) { | ||
return new MockResponse() | ||
.setResponseCode(404) | ||
.setBody("unexpected request"); | ||
} | ||
|
||
try { | ||
OAuthSession session = slackApi.exchangeCode(code, redirectUrl()); | ||
listener.sessionGranted(session); | ||
} catch (IOException e) { | ||
return new MockResponse() | ||
.setResponseCode(400) | ||
.setBody("code exchange failed: " + e.getMessage()); | ||
} | ||
|
||
synchronized (this) { | ||
listeners.remove(state); | ||
} | ||
|
||
// Success! | ||
return new MockResponse() | ||
.setResponseCode(302) | ||
.addHeader("Location", "https://twitter.com/CuteEmergency/status/789457462864863232"); | ||
} | ||
|
||
public interface Listener { | ||
void sessionGranted(OAuthSession session); | ||
} | ||
|
||
@Override public void close() { | ||
if (mockWebServer == null) throw new IllegalStateException(); | ||
try { | ||
mockWebServer.close(); | ||
} catch (IOException ignored) { | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/* | ||
* Copyright (C) 2016 Square, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package okhttp3.slack; | ||
|
||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import okhttp3.Response; | ||
import okhttp3.ResponseBody; | ||
import okhttp3.WebSocket; | ||
import okhttp3.WebSocketCall; | ||
import okhttp3.WebSocketListener; | ||
import okio.ByteString; | ||
|
||
/** A realtime messaging session. */ | ||
public final class RtmSession implements WebSocketListener, Closeable { | ||
private final SlackApi slackApi; | ||
private WebSocketCall webSocketCall; | ||
|
||
/** Guarded by this. */ | ||
private WebSocket webSocket; | ||
|
||
public RtmSession(SlackApi slackApi) { | ||
this.slackApi = slackApi; | ||
} | ||
|
||
public void open(String accessToken) throws IOException { | ||
if (webSocketCall != null) throw new IllegalStateException(); | ||
|
||
RtmStartResponse rtmStartResponse = slackApi.rtmStart(accessToken); | ||
webSocketCall = slackApi.rtm(rtmStartResponse.url); | ||
webSocketCall.enqueue(this); | ||
} | ||
|
||
// TODO(jwilson): can I read the response body? Do I have to? | ||
// the body from slack is a 0-byte-buffer | ||
@Override public synchronized void onOpen(WebSocket webSocket, Response response) { | ||
System.out.println("onOpen: " + response); | ||
this.webSocket = webSocket; | ||
} | ||
|
||
// TOOD(jwilson): decode incoming messages and dispatch them somewhere. | ||
@Override public void onMessage(ResponseBody message) throws IOException { | ||
System.out.println("onMessage: " + message.string()); | ||
} | ||
|
||
@Override public void onPong(ByteString payload) { | ||
System.out.println("onPong: " + payload); | ||
} | ||
|
||
@Override public void onClose(int code, String reason) { | ||
System.out.println("onClose (" + code + "): " + reason); | ||
} | ||
|
||
// TODO(jwilson): can I read the response body? Do I have to? | ||
@Override public void onFailure(Throwable t, Response response) { | ||
System.out.println("onFailure " + response); | ||
} | ||
|
||
@Override public void close() throws IOException { | ||
if (webSocketCall == null) return; | ||
|
||
WebSocket webSocket; | ||
synchronized (this) { | ||
webSocket = this.webSocket; | ||
} | ||
|
||
// TODO(jwilson): Racy? Is there an interleaving of events where the websocket is not closed? | ||
// Our docs say we can’t close if we have an active writer: that seems like it | ||
// could cause problems? | ||
if (webSocket != null) { | ||
webSocket.close(1000, "bye"); | ||
} else { | ||
webSocketCall.cancel(); | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
samples/slack/src/main/java/okhttp3/slack/RtmStartResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* Copyright (C) 2016 Square, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package okhttp3.slack; | ||
|
||
import java.util.List; | ||
import okhttp3.HttpUrl; | ||
|
||
/** See https://api.slack.com/methods/rtm.start. */ | ||
public final class RtmStartResponse { | ||
HttpUrl url; | ||
Object self; | ||
Object team; | ||
List<Object> users; | ||
List<Object> channels; | ||
List<Object> groups; | ||
List<Object> mpims; | ||
List<Object> ims; | ||
List<Object> bots; | ||
} |
Oops, something went wrong.