Home | History | Annotate | Download | only in curl
      1 /*
      2  * Copyright (C) 2014 Square, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.squareup.okhttp.curl;
     17 
     18 import com.google.common.base.Joiner;
     19 import com.squareup.okhttp.ConnectionPool;
     20 import com.squareup.okhttp.Headers;
     21 import com.squareup.okhttp.MediaType;
     22 import com.squareup.okhttp.OkHttpClient;
     23 import com.squareup.okhttp.Protocol;
     24 import com.squareup.okhttp.Request;
     25 import com.squareup.okhttp.RequestBody;
     26 import com.squareup.okhttp.Response;
     27 import com.squareup.okhttp.internal.http.StatusLine;
     28 import com.squareup.okhttp.internal.framed.Http2;
     29 
     30 import io.airlift.command.Arguments;
     31 import io.airlift.command.Command;
     32 import io.airlift.command.HelpOption;
     33 import io.airlift.command.Option;
     34 import io.airlift.command.SingleCommand;
     35 import java.io.IOException;
     36 import java.io.InputStream;
     37 import java.security.cert.CertificateException;
     38 import java.security.cert.X509Certificate;
     39 import java.util.List;
     40 import java.util.Properties;
     41 import java.util.logging.ConsoleHandler;
     42 import java.util.logging.Level;
     43 import java.util.logging.LogRecord;
     44 import java.util.logging.Logger;
     45 import java.util.logging.SimpleFormatter;
     46 import javax.net.ssl.HostnameVerifier;
     47 import javax.net.ssl.SSLContext;
     48 import javax.net.ssl.SSLSession;
     49 import javax.net.ssl.SSLSocketFactory;
     50 import javax.net.ssl.TrustManager;
     51 import javax.net.ssl.X509TrustManager;
     52 import okio.BufferedSource;
     53 import okio.Okio;
     54 import okio.Sink;
     55 
     56 import static java.util.concurrent.TimeUnit.SECONDS;
     57 
     58 @Command(name = Main.NAME, description = "A curl for the next-generation web.")
     59 public class Main extends HelpOption implements Runnable {
     60   static final String NAME = "okcurl";
     61   static final int DEFAULT_TIMEOUT = -1;
     62 
     63   static Main fromArgs(String... args) {
     64     return SingleCommand.singleCommand(Main.class).parse(args);
     65   }
     66 
     67   public static void main(String... args) {
     68     fromArgs(args).run();
     69   }
     70 
     71   private static String versionString() {
     72     try {
     73       Properties prop = new Properties();
     74       InputStream in = Main.class.getResourceAsStream("/okcurl-version.properties");
     75       prop.load(in);
     76       in.close();
     77       return prop.getProperty("version");
     78     } catch (IOException e) {
     79       throw new AssertionError("Could not load okcurl-version.properties.");
     80     }
     81   }
     82 
     83   private static String protocols() {
     84     return Joiner.on(", ").join(Protocol.values());
     85   }
     86 
     87   @Option(name = { "-X", "--request" }, description = "Specify request command to use")
     88   public String method;
     89 
     90   @Option(name = { "-d", "--data" }, description = "HTTP POST data")
     91   public String data;
     92 
     93   @Option(name = { "-H", "--header" }, description = "Custom header to pass to server")
     94   public List<String> headers;
     95 
     96   @Option(name = { "-A", "--user-agent" }, description = "User-Agent to send to server")
     97   public String userAgent = NAME + "/" + versionString();
     98 
     99   @Option(name = "--connect-timeout", description = "Maximum time allowed for connection (seconds)")
    100   public int connectTimeout = DEFAULT_TIMEOUT;
    101 
    102   @Option(name = "--read-timeout", description = "Maximum time allowed for reading data (seconds)")
    103   public int readTimeout = DEFAULT_TIMEOUT;
    104 
    105   @Option(name = { "-L", "--location" }, description = "Follow redirects")
    106   public boolean followRedirects;
    107 
    108   @Option(name = { "-k", "--insecure" },
    109       description = "Allow connections to SSL sites without certs")
    110   public boolean allowInsecure;
    111 
    112   @Option(name = { "-i", "--include" }, description = "Include protocol headers in the output")
    113   public boolean showHeaders;
    114 
    115   @Option(name = "--frames", description = "Log HTTP/2 frames to STDERR")
    116   public boolean showHttp2Frames;
    117 
    118   @Option(name = { "-e", "--referer" }, description = "Referer URL")
    119   public String referer;
    120 
    121   @Option(name = { "-V", "--version" }, description = "Show version number and quit")
    122   public boolean version;
    123 
    124   @Arguments(title = "url", description = "Remote resource URL")
    125   public String url;
    126 
    127   private OkHttpClient client;
    128 
    129   @Override public void run() {
    130     if (showHelpIfRequested()) {
    131       return;
    132     }
    133     if (version) {
    134       System.out.println(NAME + " " + versionString());
    135       System.out.println("Protocols: " + protocols());
    136       return;
    137     }
    138 
    139     if (showHttp2Frames) {
    140       enableHttp2FrameLogging();
    141     }
    142 
    143     client = createClient();
    144     Request request = createRequest();
    145     try {
    146       Response response = client.newCall(request).execute();
    147       if (showHeaders) {
    148         System.out.println(StatusLine.get(response));
    149         Headers headers = response.headers();
    150         for (int i = 0, size = headers.size(); i < size; i++) {
    151           System.out.println(headers.name(i) + ": " + headers.value(i));
    152         }
    153         System.out.println();
    154       }
    155 
    156       // Stream the response to the System.out as it is returned from the server.
    157       Sink out = Okio.sink(System.out);
    158       BufferedSource source = response.body().source();
    159       while (!source.exhausted()) {
    160         out.write(source.buffer(), source.buffer().size());
    161         out.flush();
    162       }
    163 
    164       response.body().close();
    165     } catch (IOException e) {
    166       e.printStackTrace();
    167     } finally {
    168       close();
    169     }
    170   }
    171 
    172   private OkHttpClient createClient() {
    173     OkHttpClient client = new OkHttpClient();
    174     client.setFollowSslRedirects(followRedirects);
    175     if (connectTimeout != DEFAULT_TIMEOUT) {
    176       client.setConnectTimeout(connectTimeout, SECONDS);
    177     }
    178     if (readTimeout != DEFAULT_TIMEOUT) {
    179       client.setReadTimeout(readTimeout, SECONDS);
    180     }
    181     if (allowInsecure) {
    182       client.setSslSocketFactory(createInsecureSslSocketFactory());
    183       client.setHostnameVerifier(createInsecureHostnameVerifier());
    184     }
    185     // If we don't set this reference, there's no way to clean shutdown persistent connections.
    186     client.setConnectionPool(ConnectionPool.getDefault());
    187     return client;
    188   }
    189 
    190   private String getRequestMethod() {
    191     if (method != null) {
    192       return method;
    193     }
    194     if (data != null) {
    195       return "POST";
    196     }
    197     return "GET";
    198   }
    199 
    200   private RequestBody getRequestBody() {
    201     if (data == null) {
    202       return null;
    203     }
    204     String bodyData = data;
    205 
    206     String mimeType = "application/x-www-form-urlencoded";
    207     if (headers != null) {
    208       for (String header : headers) {
    209         String[] parts = header.split(":", -1);
    210         if ("Content-Type".equalsIgnoreCase(parts[0])) {
    211           mimeType = parts[1].trim();
    212           headers.remove(header);
    213           break;
    214         }
    215       }
    216     }
    217 
    218     return RequestBody.create(MediaType.parse(mimeType), bodyData);
    219   }
    220 
    221   Request createRequest() {
    222     Request.Builder request = new Request.Builder();
    223 
    224     request.url(url);
    225     request.method(getRequestMethod(), getRequestBody());
    226 
    227     if (headers != null) {
    228       for (String header : headers) {
    229         String[] parts = header.split(":", 2);
    230         request.header(parts[0], parts[1]);
    231       }
    232     }
    233     if (referer != null) {
    234       request.header("Referer", referer);
    235     }
    236     request.header("User-Agent", userAgent);
    237 
    238     return request.build();
    239   }
    240 
    241   private void close() {
    242     client.getConnectionPool().evictAll(); // Close any persistent connections.
    243   }
    244 
    245   private static SSLSocketFactory createInsecureSslSocketFactory() {
    246     try {
    247       SSLContext context = SSLContext.getInstance("TLS");
    248       TrustManager permissive = new X509TrustManager() {
    249         @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
    250             throws CertificateException {
    251         }
    252 
    253         @Override public void checkServerTrusted(X509Certificate[] chain, String authType)
    254             throws CertificateException {
    255         }
    256 
    257         @Override public X509Certificate[] getAcceptedIssuers() {
    258           return null;
    259         }
    260       };
    261       context.init(null, new TrustManager[] { permissive }, null);
    262       return context.getSocketFactory();
    263     } catch (Exception e) {
    264       throw new AssertionError(e);
    265     }
    266   }
    267 
    268   private static HostnameVerifier createInsecureHostnameVerifier() {
    269     return new HostnameVerifier() {
    270       @Override public boolean verify(String s, SSLSession sslSession) {
    271         return true;
    272       }
    273     };
    274   }
    275 
    276   private static void enableHttp2FrameLogging() {
    277     Logger logger = Logger.getLogger(Http2.class.getName() + "$FrameLogger");
    278     logger.setLevel(Level.FINE);
    279     ConsoleHandler handler = new ConsoleHandler();
    280     handler.setLevel(Level.FINE);
    281     handler.setFormatter(new SimpleFormatter() {
    282       @Override public String format(LogRecord record) {
    283         return String.format("%s%n", record.getMessage());
    284       }
    285     });
    286     logger.addHandler(handler);
    287   }
    288 }
    289