-
Notifications
You must be signed in to change notification settings - Fork 9.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation of an OkHttp-backed curl clone. #514
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
<?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.okhttp</groupId> | ||
<artifactId>parent</artifactId> | ||
<version>2.0.0-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>okcurl</artifactId> | ||
<name>OkCurl</name> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.squareup.okhttp</groupId> | ||
<artifactId>okhttp</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.bouncycastle</groupId> | ||
<artifactId>bcprov-jdk15on</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mortbay.jetty.npn</groupId> | ||
<artifactId>npn-boot</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.airlift</groupId> | ||
<artifactId>airline</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<resources> | ||
<resource> | ||
<directory>src/main/resources</directory> | ||
<filtering>true</filtering> | ||
</resource> | ||
</resources> | ||
|
||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-assembly-plugin</artifactId> | ||
<configuration> | ||
<descriptorRefs> | ||
<descriptorRef>jar-with-dependencies</descriptorRef> | ||
</descriptorRefs> | ||
<archive> | ||
<manifest> | ||
<mainClass>com.squareup.okhttp.curl.Main</mainClass> | ||
</manifest> | ||
</archive> | ||
</configuration> | ||
<executions> | ||
<execution> | ||
<phase>package</phase> | ||
<goals> | ||
<goal>single</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.skife.maven</groupId> | ||
<artifactId>really-executable-jar-maven-plugin</artifactId> | ||
<version>1.1.0</version> | ||
<executions> | ||
<execution> | ||
<phase>package</phase> | ||
<goals> | ||
<goal>really-executable-jar</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
<configuration> | ||
<flags>-Xbootclasspath/p:$0</flags> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
package com.squareup.okhttp.curl; | ||
|
||
import com.google.common.base.Function; | ||
import com.google.common.base.Joiner; | ||
import com.google.common.collect.Lists; | ||
import com.squareup.okhttp.ConnectionPool; | ||
import com.squareup.okhttp.Failure; | ||
import com.squareup.okhttp.Headers; | ||
import com.squareup.okhttp.OkHttpClient; | ||
import com.squareup.okhttp.Protocol; | ||
import com.squareup.okhttp.Request; | ||
import com.squareup.okhttp.Response; | ||
import io.airlift.command.Arguments; | ||
import io.airlift.command.Command; | ||
import io.airlift.command.HelpOption; | ||
import io.airlift.command.Option; | ||
import io.airlift.command.SingleCommand; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.security.cert.CertificateException; | ||
import java.security.cert.X509Certificate; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Properties; | ||
import javax.net.ssl.SSLContext; | ||
import javax.net.ssl.SSLSocketFactory; | ||
import javax.net.ssl.TrustManager; | ||
import javax.net.ssl.X509TrustManager; | ||
|
||
import static java.util.concurrent.TimeUnit.SECONDS; | ||
|
||
@Command(name = Main.NAME, description = "A curl for the next-generation web.") | ||
public class Main extends HelpOption implements Runnable, Response.Receiver { | ||
static final String NAME = "okcurl"; | ||
static final int DEFAULT_TIMEOUT = -1; | ||
|
||
public static void main(String... args) { | ||
SingleCommand.singleCommand(Main.class).parse(args).run(); | ||
} | ||
|
||
private static String versionString() { | ||
try { | ||
Properties prop = new Properties(); | ||
InputStream in = Main.class.getResourceAsStream("/okcurl-version.properties"); | ||
prop.load(in); | ||
in.close(); | ||
return prop.getProperty("version"); | ||
} catch (IOException e) { | ||
throw new AssertionError("Could not load okcurl-version.properties."); | ||
} | ||
} | ||
|
||
private static String protocols() { | ||
return Joiner.on(", ").join(Lists.transform(Arrays.asList(Protocol.values()), | ||
new Function<Protocol, String>() { | ||
@Override public String apply(Protocol protocol) { | ||
return protocol.name.utf8(); | ||
} | ||
})); | ||
} | ||
|
||
@Option(name = { "-H", "--header" }, description = "Custom header to pass to server") | ||
public List<String> headers; | ||
|
||
@Option(name = { "-A", "--user-agent" }, description = "User-Agent to send to server") | ||
public String userAgent = NAME + "/" + versionString(); | ||
|
||
@Option(name = "--connect-timeout", description = "Maximum time allowed for connection (seconds)") | ||
public int connectTimeout = DEFAULT_TIMEOUT; | ||
|
||
@Option(name = "--read-timeout", description = "Maximum time allowed for reading data (seconds)") | ||
public int readTimeout = DEFAULT_TIMEOUT; | ||
|
||
@Option(name = { "-L", "--location" }, description = "Follow redirects") | ||
public boolean followRedirects; | ||
|
||
@Option(name = { "-k", "--insecure" }, | ||
description = "Allow connections to SSL sites without certs") | ||
public boolean allowInsecure; | ||
|
||
@Option(name = { "-i", "--include" }, description = "Include protocol headers in the output") | ||
public boolean showHeaders; | ||
|
||
@Option(name = { "-e", "--referer" }, description = "Referer URL") | ||
public String referer; | ||
|
||
@Option(name = { "-V", "--version" }, description = "Show version number and quit") | ||
public boolean version; | ||
|
||
@Arguments(title = "url", description = "Remote resource URL") | ||
public String url; | ||
|
||
private OkHttpClient client; | ||
|
||
@Override public void run() { | ||
if (showHelpIfRequested()) { | ||
return; | ||
} | ||
if (version) { | ||
System.out.println(NAME + " " + versionString()); | ||
System.out.println("Protocols: " + protocols()); | ||
return; | ||
} | ||
|
||
client = getConfiguredClient(); | ||
Request request = getConfiguredRequest(); | ||
client.enqueue(request, this); | ||
|
||
// Immediately begin triggering an executor shutdown so that after execution of the above | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm. . . policy here is tricky. I don't think they should be daemon threads unless they're idle. Unfortunately that isn't an option. Dumb. |
||
// request the threads do not stick around until timeout. | ||
client.getDispatcher().getExecutorService().shutdown(); | ||
} | ||
|
||
private OkHttpClient getConfiguredClient() { | ||
OkHttpClient client = new OkHttpClient(); | ||
client.setFollowProtocolRedirects(followRedirects); | ||
if (connectTimeout != DEFAULT_TIMEOUT) { | ||
client.setConnectTimeout(connectTimeout, SECONDS); | ||
} | ||
if (readTimeout != DEFAULT_TIMEOUT) { | ||
client.setReadTimeout(readTimeout, SECONDS); | ||
} | ||
if (allowInsecure) { | ||
client.setSslSocketFactory(createInsecureSslSocketFactory()); | ||
} | ||
// If we don't set this reference, there's no way to clean shutdown persistent connections. | ||
client.setConnectionPool(ConnectionPool.getDefault()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we don't set this reference, there's no way to clean shutdown persistent connections There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add as comment? |
||
return client; | ||
} | ||
|
||
private Request getConfiguredRequest() { | ||
Request.Builder request = new Request.Builder(); | ||
request.url(url); | ||
if (headers != null) { | ||
for (String header : headers) { | ||
String[] parts = header.split(":", -1); | ||
request.header(parts[0], parts[1]); | ||
} | ||
} | ||
if (referer != null) { | ||
request.header("Referer", referer); | ||
} | ||
request.header("User-Agent", userAgent); | ||
return request.build(); | ||
} | ||
|
||
@Override public void onFailure(Failure failure) { | ||
failure.exception().printStackTrace(); | ||
close(); | ||
} | ||
|
||
@Override public boolean onResponse(Response response) throws IOException { | ||
if (showHeaders) { | ||
System.out.println(response.statusLine()); | ||
Headers headers = response.headers(); | ||
for (int i = 0, count = headers.size(); i < count; i++) { | ||
System.out.println(headers.name(i) + ": " + headers.value(i)); | ||
} | ||
System.out.println(); | ||
} | ||
|
||
Response.Body body = response.body(); | ||
byte[] buffer = new byte[1024]; | ||
while (body.ready()) { | ||
int c = body.byteStream().read(buffer); | ||
if (c == -1) { | ||
close(); | ||
return true; | ||
} | ||
|
||
System.out.write(buffer, 0, c); | ||
} | ||
close(); | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For what it's worth, I'm going to axe the boolean returns here. It's just too weird. |
||
} | ||
|
||
private void close() { | ||
client.getConnectionPool().evictAll(); // Close any persistent connections. | ||
} | ||
|
||
private static SSLSocketFactory createInsecureSslSocketFactory() { | ||
try { | ||
SSLContext context = SSLContext.getInstance("TLS"); | ||
TrustManager permissive = new X509TrustManager() { | ||
@Override public void checkClientTrusted(X509Certificate[] chain, String authType) | ||
throws CertificateException { | ||
} | ||
|
||
@Override public void checkServerTrusted(X509Certificate[] chain, String authType) | ||
throws CertificateException { | ||
} | ||
|
||
@Override public X509Certificate[] getAcceptedIssuers() { | ||
return null; | ||
} | ||
}; | ||
context.init(null, new TrustManager[] { permissive }, null); | ||
return context.getSocketFactory(); | ||
} catch (Exception e) { | ||
throw new AssertionError(e); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
version=${project.version} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note you'll probably need to add something like below or NPN won't work.
You can test this by running your curl against https://twitter.com and viewing the
OkHttp-Selected-Protocol
. If it is working, then you will seeHTTP-draft-09/2.0
orspdy/3.1
instead ofhttp/1.1
.@brianm ^^ likely to work?