Compare commits
144 Commits
libproject
...
pluggable_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22b1959333 | ||
|
|
97008305ff | ||
|
|
1a17083e8c | ||
|
|
b6664cc859 | ||
|
|
50c4aef873 | ||
|
|
cf42d31214 | ||
|
|
00caa1c0a0 | ||
|
|
93c8ba920a | ||
|
|
8702c04d39 | ||
|
|
e595c313a1 | ||
|
|
955da2e360 | ||
|
|
04b3fc0268 | ||
|
|
105ccc81a5 | ||
|
|
c349892c5b | ||
|
|
3d4b8ce99b | ||
|
|
3571307df5 | ||
|
|
64fd87134f | ||
|
|
82bba44538 | ||
|
|
df05f3a3c0 | ||
|
|
8e31ef7be6 | ||
|
|
f4555f7c96 | ||
|
|
8408da55ea | ||
|
|
4a67dd2e28 | ||
|
|
bd806a34d8 | ||
|
|
2f7e833a79 | ||
|
|
c17503ab78 | ||
|
|
19f76d34db | ||
|
|
25c8b2fabb | ||
|
|
bfd8bf9ca4 | ||
|
|
e6adbb0e42 | ||
|
|
11fc6be328 | ||
|
|
7a5405d2ab | ||
|
|
b9a24f00ad | ||
|
|
dbfc292353 | ||
|
|
a09255b2ff | ||
|
|
9d1c72cc07 | ||
|
|
09ac30ef2e | ||
|
|
0ec8f5d283 | ||
|
|
b872df0f31 | ||
|
|
0add4af208 | ||
|
|
298cd9e065 | ||
|
|
b715d20385 | ||
|
|
79e313a0c0 | ||
|
|
9f4c75d1c2 | ||
|
|
b37492644c | ||
|
|
04a792a8c2 | ||
|
|
35ec24c3f0 | ||
|
|
9a00ccdacc | ||
|
|
61b23677d1 | ||
|
|
90037dc6cd | ||
|
|
e8d48e1f43 | ||
|
|
a2f8c9c75b | ||
|
|
5fb83e7f52 | ||
|
|
dd6bf568d1 | ||
|
|
00ee164cef | ||
|
|
448071b02d | ||
|
|
4dad9d0e37 | ||
|
|
3a2117c5d3 | ||
|
|
26a3f6ddc3 | ||
|
|
7741312673 | ||
|
|
954a1723f1 | ||
|
|
87285d94f7 | ||
|
|
d260d0c182 | ||
|
|
137eb40fab | ||
|
|
af440460e1 | ||
|
|
a5c8472a37 | ||
|
|
dfae37421d | ||
|
|
438a8d8b75 | ||
|
|
ac2034561d | ||
|
|
c42cd4233d | ||
|
|
5b2a73e3eb | ||
|
|
6f163a6ba5 | ||
|
|
cc94cc7d01 | ||
|
|
94934ae2cf | ||
|
|
e361f88501 | ||
|
|
708c042b61 | ||
|
|
600599f49e | ||
|
|
1fe7bbbbc4 | ||
|
|
f83d7a7cd1 | ||
|
|
7094047b3d | ||
|
|
11d3607688 | ||
|
|
fcae58d355 | ||
|
|
ef9ace9e65 | ||
|
|
22e4039133 | ||
|
|
4971670e56 | ||
|
|
e16cab6b9c | ||
|
|
a643c3dba6 | ||
|
|
51abf5b0a6 | ||
|
|
1cee6e309b | ||
|
|
0777a660bf | ||
|
|
5e0479e414 | ||
|
|
942c77816d | ||
|
|
8e260d5c40 | ||
|
|
7951eee8a3 | ||
|
|
be2f7d7a8a | ||
|
|
59c8e8b46e | ||
|
|
98c8b28bf3 | ||
|
|
7bb5bc01b7 | ||
|
|
1482c07ae4 | ||
|
|
adba84ae6a | ||
|
|
146e296826 | ||
|
|
28c10dba09 | ||
|
|
e646a0840d | ||
|
|
74ea6bf00a | ||
|
|
d7ad784809 | ||
|
|
642bd10dcc | ||
|
|
7c566c36f4 | ||
|
|
dbbe038939 | ||
|
|
e3430a916c | ||
|
|
ea1f041e11 | ||
|
|
0fe6d9f367 | ||
|
|
46e7359372 | ||
|
|
41cace9a96 | ||
|
|
4638331cb4 | ||
|
|
e339a7583c | ||
|
|
0b7570c9ee | ||
|
|
a85acfcfc5 | ||
|
|
3d4ccbec23 | ||
|
|
2f66ec60db | ||
|
|
f1cfe2b07b | ||
|
|
6160ca6e30 | ||
|
|
b621c3e4c4 | ||
|
|
64d2ae9ad4 | ||
|
|
39fc45b8d8 | ||
|
|
fd954adc81 | ||
|
|
8b379cbf56 | ||
|
|
59c0b04602 | ||
|
|
11b3cf3bfd | ||
|
|
9254f5a8a5 | ||
|
|
e5b68f4a3c | ||
|
|
95babc01e3 | ||
|
|
4dd792a49f | ||
|
|
207c50e500 | ||
|
|
763e34e861 | ||
|
|
b895a0c335 | ||
|
|
c5767eb545 | ||
|
|
94fb79c17f | ||
|
|
6856b02aa7 | ||
|
|
04f812c136 | ||
|
|
8783cf03b2 | ||
|
|
4e1156e083 | ||
|
|
1d6e1d416b | ||
|
|
fd02e5a07e | ||
|
|
937056fcaf |
5
.gitignore
vendored
@@ -14,11 +14,16 @@ framework/assets/www/.DS_Store
|
|||||||
framework/assets/www/cordova-*.js
|
framework/assets/www/cordova-*.js
|
||||||
framework/assets/www/phonegap-*.js
|
framework/assets/www/phonegap-*.js
|
||||||
framework/libs
|
framework/libs
|
||||||
|
framework/javadoc-public
|
||||||
|
framework/javadoc-private
|
||||||
test/libs
|
test/libs
|
||||||
example
|
example
|
||||||
./test
|
./test
|
||||||
test/bin
|
test/bin
|
||||||
test/assets/www/.tmp*
|
test/assets/www/.tmp*
|
||||||
|
test/assets/www/cordova.js
|
||||||
|
test/cordova/plugins/org.apache.cordova.device/www/device.js
|
||||||
|
test/cordova/plugins/org.apache.cordova.device/src/android/Device.java
|
||||||
tmp/**
|
tmp/**
|
||||||
.metadata
|
.metadata
|
||||||
tmp/**/*
|
tmp/**/*
|
||||||
|
|||||||
16
CONTRIBUTING.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Contributing to Apache Cordova
|
||||||
|
|
||||||
|
Anyone can contribute to Cordova. And we need your contributions.
|
||||||
|
|
||||||
|
There are multiple ways to contribute: report bugs, improve the docs, and
|
||||||
|
contribute code.
|
||||||
|
|
||||||
|
For instructions on this, start with the
|
||||||
|
[contribution overview](http://cordova.apache.org/#contribute).
|
||||||
|
|
||||||
|
The details are explained there, but the important items are:
|
||||||
|
- Sign and submit an Apache ICLA (Contributor License Agreement).
|
||||||
|
- Have a Jira issue open that corresponds to your contribution.
|
||||||
|
- Run the tests so your patch doesn't break existing functionality.
|
||||||
|
|
||||||
|
We look forward to your contributions!
|
||||||
8
NOTICE
@@ -1,5 +1,5 @@
|
|||||||
Apache Cordova
|
Apache Cordova
|
||||||
Copyright 2012 The Apache Software Foundation
|
Copyright 2014 The Apache Software Foundation
|
||||||
|
|
||||||
This product includes software developed at
|
This product includes software developed at
|
||||||
The Apache Software Foundation (http://www.apache.org)
|
The Apache Software Foundation (http://www.apache.org)
|
||||||
@@ -10,8 +10,8 @@ The Apache Software Foundation (http://www.apache.org)
|
|||||||
== in this case for the Android-specific code. ==
|
== in this case for the Android-specific code. ==
|
||||||
=========================================================================
|
=========================================================================
|
||||||
|
|
||||||
Android Code
|
|
||||||
Copyright 2005-2008 The Android Open Source Project
|
|
||||||
|
|
||||||
This product includes software developed as part of
|
This product includes software developed as part of
|
||||||
The Android Open Source Project (http://source.android.com).
|
The Android Open Source Project (http://source.android.com).
|
||||||
|
|
||||||
|
This software includes software developed at Square, Inc.
|
||||||
|
Copyright (C) 2013 Square, Inc.
|
||||||
|
|||||||
24
README.md
@@ -7,9 +7,9 @@
|
|||||||
# to you under the Apache License, Version 2.0 (the
|
# to you under the Apache License, Version 2.0 (the
|
||||||
# "License"); you may not use this file except in compliance
|
# "License"); you may not use this file except in compliance
|
||||||
# with the License. You may obtain a copy of the License at
|
# with the License. You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing,
|
# Unless required by applicable law or agreed to in writing,
|
||||||
# software distributed under the License is distributed on an
|
# software distributed under the License is distributed on an
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
@@ -23,27 +23,27 @@ Cordova Android
|
|||||||
|
|
||||||
Cordova Android is an Android application library that allows for Cordova-based
|
Cordova Android is an Android application library that allows for Cordova-based
|
||||||
projects to be built for the Android Platform. Cordova based applications are,
|
projects to be built for the Android Platform. Cordova based applications are,
|
||||||
at the core, applications written with web technology: HTML, CSS and JavaScript.
|
at the core, applications written with web technology: HTML, CSS and JavaScript.
|
||||||
|
|
||||||
[Apache Cordova](http://cordova.io) is a project at The Apache Software Foundation (ASF).
|
[Apache Cordova](http://cordova.io) is a project of The Apache Software Foundation (ASF).
|
||||||
|
|
||||||
|
|
||||||
Requires
|
Requires
|
||||||
---
|
---
|
||||||
|
|
||||||
- Java JDK 1.5 or greater
|
- Java JDK 1.5 or greater
|
||||||
- Apache ANT 1.8.0 or greater
|
- Apache Ant 1.8.0 or greater
|
||||||
- Android SDK [http://developer.android.com](http://developer.android.com)
|
- Android SDK [http://developer.android.com](http://developer.android.com)
|
||||||
|
|
||||||
|
|
||||||
Cordova Android Developer Tools
|
Cordova Android Developer Tools
|
||||||
---
|
---
|
||||||
|
|
||||||
The Cordova developer tooling is split between general tooling and project level tooling.
|
The Cordova developer tooling is split between general tooling and project level tooling.
|
||||||
|
|
||||||
General Commands
|
General Commands
|
||||||
|
|
||||||
./bin/create [path package activity] ... create the ./example app or a cordova android project
|
./bin/create [path package activity] ... creates the ./example app or a cordova android project
|
||||||
./bin/check_reqs ....................... checks that your environment is set up for cordova-android development
|
./bin/check_reqs ....................... checks that your environment is set up for cordova-android development
|
||||||
./bin/update [path] .................... updates an existing cordova-android project to the version of the framework
|
./bin/update [path] .................... updates an existing cordova-android project to the version of the framework
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ These commands live in a generated Cordova Android project. Any interactions wit
|
|||||||
|
|
||||||
./cordova/clean ........................ cleans the project
|
./cordova/clean ........................ cleans the project
|
||||||
./cordova/build ........................ calls `clean` then compiles the project
|
./cordova/build ........................ calls `clean` then compiles the project
|
||||||
./cordova/log ........................ stream device or emulate logs to stdout
|
./cordova/log ........................ streams device or emulator logs to STDOUT
|
||||||
./cordova/run ........................ calls `build` then deploys to a connected Android device. If no Android device is detected, will launch an emulator and deploy to it.
|
./cordova/run ........................ calls `build` then deploys to a connected Android device. If no Android device is detected, will launch an emulator and deploy to it.
|
||||||
./cordova/version ...................... returns the cordova-android version of the current project
|
./cordova/version ...................... returns the cordova-android version of the current project
|
||||||
|
|
||||||
@@ -69,13 +69,13 @@ Importing a Cordova Android Project into Eclipse
|
|||||||
|
|
||||||
Building without the Tooling
|
Building without the Tooling
|
||||||
---
|
---
|
||||||
Note: The Developer Tools handle this. This is only to be done if the tooling fails, or if
|
Note: The Developer Tools handle this. This is only to be done if the tooling fails, or if
|
||||||
you are developing directly against the framework.
|
you are developing directly against the framework.
|
||||||
|
|
||||||
|
|
||||||
To create your `cordova.jar` file, run in the framework directory:
|
To create your `cordova.jar` file, run in the framework directory:
|
||||||
|
|
||||||
android update project -p . -t android-18
|
android update project -p . -t android-19
|
||||||
ant jar
|
ant jar
|
||||||
|
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ Running Tests
|
|||||||
Please see details under test/README.md.
|
Please see details under test/README.md.
|
||||||
|
|
||||||
Further Reading
|
Further Reading
|
||||||
---
|
----
|
||||||
|
|
||||||
- [http://developer.android.com](http://developer.android.com)
|
- [http://developer.android.com](http://developer.android.com)
|
||||||
- [http://cordova.apache.org/](http://cordova.apache.org)
|
- [http://cordova.apache.org/](http://cordova.apache.org)
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
# to you under the Apache License, Version 2.0 (the
|
# to you under the Apache License, Version 2.0 (the
|
||||||
# "License"); you may not use this file except in compliance
|
# "License"); you may not use this file except in compliance
|
||||||
# with the License. You may obtain a copy of the License at
|
# with the License. You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing,
|
# Unless required by applicable law or agreed to in writing,
|
||||||
# software distributed under the License is distributed on an
|
# software distributed under the License is distributed on an
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
@@ -20,6 +20,55 @@
|
|||||||
-->
|
-->
|
||||||
## Release Notes for Cordova (Android) ##
|
## Release Notes for Cordova (Android) ##
|
||||||
|
|
||||||
|
### 3.4.0 (Feb 2014) ###
|
||||||
|
|
||||||
|
43 commits from 10 authors. Highlights include:
|
||||||
|
|
||||||
|
* Removing addJavascriptInterface support from all Android versions lower than 4.2 due to security vulnerability
|
||||||
|
* CB-5917 Add a loadUrlIntoView overload that doesn't recreate plugins.
|
||||||
|
* CB-5889 Make update script find project name instead of using "null" for CordovaLib
|
||||||
|
* CB-5889 Add a message in the update script about needing to import CordovaLib when using an IDE.
|
||||||
|
* CB-5793 Don't clean before build and change output directory to ant-build to avoid conflicts with Eclipse.
|
||||||
|
* CB-5803 Fix cordova/emulate on windows.
|
||||||
|
* CB-5801 exec->spawn in build to make sure compile errors are shown.
|
||||||
|
* CB-5799 Update version of OkHTTP to 1.3
|
||||||
|
* CB-4910 Update CLI project template to point to config.xml at the root now that it's not in www/ by default.
|
||||||
|
* CB-5504 Adding onDestroy to app plugin to deregister telephonyReceiver
|
||||||
|
* CB-5715 Add Eclipse .project file to create template. For CLI projects, it adds refs for root www/ & config.xml and hides platform versions
|
||||||
|
* CB-5447 Removed android:debuggable=“true” from project template.
|
||||||
|
* CB-5714 Fix of android build when too big output stops build with error due to buffer overflow.
|
||||||
|
* CB-5592 Set MIME type for openExternal when scheme is file:
|
||||||
|
|
||||||
|
### 3.3.0 (Dec 2013) ###
|
||||||
|
|
||||||
|
41 commits from 11 authors. Highlights include:
|
||||||
|
|
||||||
|
* CB-5481 Fix for Cordova trying to get config.xml from the wrong namespace
|
||||||
|
* CB-5487 Enable Remote Debugging when your Android app is debuggable.
|
||||||
|
* CB-5445 Adding onScrollChanged and the ScrollEvent object
|
||||||
|
* CB-5422 Don't require JAVA_HOME to be defined
|
||||||
|
* CB-5490 Add javadoc target to ant script
|
||||||
|
* CB-5471 Deprecated DroidGap class
|
||||||
|
* CB-5255 Prefer Google API targets over android-## targets when building.
|
||||||
|
* CB-5232 Change create script to use Cordova as a Library Project instead of a .jar
|
||||||
|
* CB-5302 Massive movement to get tests working again
|
||||||
|
* CB-4996 Fix paths with spaces while launching on emulator and device
|
||||||
|
* CB-5209 Cannot build Android app if project path contains spaces
|
||||||
|
|
||||||
|
|
||||||
|
### 3.2.0 (Nov 2013) ###
|
||||||
|
|
||||||
|
27 commits from 7 authors. Highlights include:
|
||||||
|
|
||||||
|
* CB-5193 Fix Android WebSQL sometime throwing SECURITY_ERR.
|
||||||
|
* CB-5191 Deprecate <url-filter>
|
||||||
|
* Updating shelljs to 0.2.6. Copy now preserves mode bits.
|
||||||
|
* CB-4872 Added android version scripts (android_sdk_version, etc)
|
||||||
|
* CB-5117 Output confirmation message if check_reqs passes.
|
||||||
|
* CB-5080 Find resources in a way that works with aapt's --rename-manifest-package
|
||||||
|
* CB-4527 Don't delete .bat files even when on non-windows platform
|
||||||
|
* CB-4892 Fix create script only escaping the first space instead of all spaces.
|
||||||
|
|
||||||
### 3.1.0 (Sept 2013) ###
|
### 3.1.0 (Sept 2013) ###
|
||||||
|
|
||||||
55 commits from 9 authors. Highlights include:
|
55 commits from 9 authors. Highlights include:
|
||||||
@@ -32,7 +81,7 @@
|
|||||||
* [CB-4764] Deprecated DirectoryManager.java (moved into plugins)
|
* [CB-4764] Deprecated DirectoryManager.java (moved into plugins)
|
||||||
* [CB-4763] Deprecated FileHelper.java (moved into plugins), Move getMimeType() into CordovaResourceApi.
|
* [CB-4763] Deprecated FileHelper.java (moved into plugins), Move getMimeType() into CordovaResourceApi.
|
||||||
* [CB-4725] Add CordovaWebView.CORDOVA_VERSION constant
|
* [CB-4725] Add CordovaWebView.CORDOVA_VERSION constant
|
||||||
* Incremeting version check for Android 4.3 API Level 18
|
* Incrementing version check for Android 4.3 API Level 18
|
||||||
* [CB-3542] rewrote cli tooling scripts in node
|
* [CB-3542] rewrote cli tooling scripts in node
|
||||||
* Allow CordovaChromeClient subclasses access to CordovaInterface and CordovaWebView members
|
* Allow CordovaChromeClient subclasses access to CordovaInterface and CordovaWebView members
|
||||||
* Refactor CordovaActivity.init so that subclasses can easily override factory methods for webview objects
|
* Refactor CordovaActivity.init so that subclasses can easily override factory methods for webview objects
|
||||||
|
|||||||
@@ -21,8 +21,11 @@
|
|||||||
|
|
||||||
var check_reqs = require('./lib/check_reqs');
|
var check_reqs = require('./lib/check_reqs');
|
||||||
|
|
||||||
check_reqs.run().done(null, function(err) {
|
check_reqs.run().done(
|
||||||
console.log(err);
|
function success() {
|
||||||
process.exit(2);
|
console.log('Looks like your environment fully supports cordova-android development!');
|
||||||
});
|
}, function fail(err) {
|
||||||
|
console.log(err);
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -32,5 +32,5 @@ if (args['--help'] || args._.length === 0) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
create.createProject(args._[0], args._[1], args._[2], args._[3], args['--shared']).done();
|
create.createProject(args._[0], args._[1], args._[2], args._[3], args['--shared'], args['--cli']).done();
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ module.exports.get_target = function() {
|
|||||||
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
|
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
|
||||||
} else if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
|
} else if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
|
||||||
// if no target found, we're probably in a project and project.properties is in ROOT.
|
// if no target found, we're probably in a project and project.properties is in ROOT.
|
||||||
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties'));
|
// this is called on the project itself, and can support Google APIs AND Vanilla Android
|
||||||
return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
|
var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) ||
|
||||||
|
shell.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties'));
|
||||||
|
return target.split('=')[1].replace('\n', '').replace('\r', '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,16 +52,18 @@ module.exports.check_ant = function() {
|
|||||||
|
|
||||||
// Returns a promise.
|
// Returns a promise.
|
||||||
module.exports.check_java = function() {
|
module.exports.check_java = function() {
|
||||||
if(process.env.JAVA_HOME) {
|
var d = Q.defer();
|
||||||
var d = Q.defer();
|
child_process.exec('java -version', function(err, stdout, stderr) {
|
||||||
child_process.exec('java -version', function(err, stdout, stderr) {
|
if(err) {
|
||||||
if(err) d.reject(new Error('ERROR : executing command \'java\', make sure you java environment is set up. Including your JDK and JRE.' + err));
|
var msg =
|
||||||
else d.resolve();
|
'Failed to run \'java -version\', make sure your java environment is set up\n' +
|
||||||
});
|
'including JDK and JRE.\n' +
|
||||||
return d.promise;
|
'Your JAVA_HOME variable is ' + process.env.JAVA_HOME + '\n';
|
||||||
} else {
|
d.reject(new Error(msg + err));
|
||||||
return Q.reject(new Error('ERROR : Make sure JAVA_HOME is set, as well as paths to your JDK and JRE for java.'));
|
}
|
||||||
}
|
else d.resolve();
|
||||||
|
});
|
||||||
|
return d.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a promise.
|
// Returns a promise.
|
||||||
@@ -87,8 +91,6 @@ module.exports.check_android = function() {
|
|||||||
|
|
||||||
// Returns a promise.
|
// Returns a promise.
|
||||||
module.exports.run = function() {
|
module.exports.run = function() {
|
||||||
return Q.all([this.check_ant(), this.check_java(), this.check_android()]).then(function() {
|
return Q.all([this.check_ant(), this.check_java(), this.check_android()]);
|
||||||
console.log('Looks like your environment fully supports cordova-android development!');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,11 @@ function runAndroidUpdate(projectPath, target_api, shared) {
|
|||||||
return exec('android update project --subprojects --path "' + projectPath + '" --target ' + target_api + ' --library "' + path.relative(projectPath, targetFrameworkDir) + '"');
|
return exec('android update project --subprojects --path "' + projectPath + '" --target ' + target_api + ' --library "' + path.relative(projectPath, targetFrameworkDir) + '"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyAntRules(projectPath) {
|
||||||
|
var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
|
||||||
|
shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath);
|
||||||
|
}
|
||||||
|
|
||||||
function copyScripts(projectPath) {
|
function copyScripts(projectPath) {
|
||||||
var srcScriptsDir = path.join(ROOT, 'bin', 'templates', 'cordova');
|
var srcScriptsDir = path.join(ROOT, 'bin', 'templates', 'cordova');
|
||||||
var destScriptsDir = path.join(projectPath, 'cordova');
|
var destScriptsDir = path.join(projectPath, 'cordova');
|
||||||
@@ -116,7 +121,7 @@ function copyScripts(projectPath) {
|
|||||||
* Returns a promise.
|
* Returns a promise.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.createProject = function(project_path, package_name, project_name, project_template_dir, use_shared_project) {
|
exports.createProject = function(project_path, package_name, project_name, project_template_dir, use_shared_project, use_cli_template) {
|
||||||
var VERSION = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
var VERSION = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
||||||
|
|
||||||
// Set default values for path, package and name
|
// Set default values for path, package and name
|
||||||
@@ -144,6 +149,10 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
|||||||
return Q.reject('Package name must look like: com.company.Name');
|
return Q.reject('Package name must look like: com.company.Name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (project_name === 'CordovaActivity') {
|
||||||
|
return Q.reject('Project name cannot be CordovaActivity');
|
||||||
|
}
|
||||||
|
|
||||||
// Check that requirements are met and proper targets are installed
|
// Check that requirements are met and proper targets are installed
|
||||||
return check_reqs.run()
|
return check_reqs.run()
|
||||||
.then(function() {
|
.then(function() {
|
||||||
@@ -160,11 +169,23 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
|||||||
// copy project template
|
// copy project template
|
||||||
shell.cp('-r', path.join(project_template_dir, 'assets'), project_path);
|
shell.cp('-r', path.join(project_template_dir, 'assets'), project_path);
|
||||||
shell.cp('-r', path.join(project_template_dir, 'res'), project_path);
|
shell.cp('-r', path.join(project_template_dir, 'res'), project_path);
|
||||||
|
shell.cp('-r', path.join(ROOT, 'framework', 'res', 'xml'), path.join(project_path, 'res'));
|
||||||
|
|
||||||
// Manually create directories that would be empty within the template (since git doesn't track directories).
|
// Manually create directories that would be empty within the template (since git doesn't track directories).
|
||||||
shell.mkdir(path.join(project_path, 'libs'));
|
shell.mkdir(path.join(project_path, 'libs'));
|
||||||
|
|
||||||
// copy cordova.js, cordova.jar and res/xml
|
// Add in the proper eclipse project file.
|
||||||
shell.cp('-r', path.join(ROOT, 'framework', 'res', 'xml'), path.join(project_path, 'res'));
|
if (use_cli_template) {
|
||||||
|
var note = 'To show `assets/www` or `res/xml/config.xml`, go to:\n' +
|
||||||
|
' Project -> Properties -> Resource -> Resource Filters\n' +
|
||||||
|
'And delete the exclusion filter.\n';
|
||||||
|
shell.cp(path.join(project_template_dir, 'eclipse-project-CLI'), path.join(project_path, '.project'));
|
||||||
|
fs.writeFileSync(path.join(project_path, 'assets', '_where-is-www.txt'), note);
|
||||||
|
} else {
|
||||||
|
shell.cp(path.join(project_template_dir, 'eclipse-project'), path.join(project_path, '.project'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy cordova.js, cordova.jar
|
||||||
copyJsAndLibrary(project_path, use_shared_project, safe_activity_name);
|
copyJsAndLibrary(project_path, use_shared_project, safe_activity_name);
|
||||||
|
|
||||||
// interpolate the activity name and package
|
// interpolate the activity name and package
|
||||||
@@ -172,6 +193,7 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
|||||||
shell.cp('-f', path.join(project_template_dir, 'Activity.java'), activity_path);
|
shell.cp('-f', path.join(project_template_dir, 'Activity.java'), activity_path);
|
||||||
shell.sed('-i', /__ACTIVITY__/, safe_activity_name, activity_path);
|
shell.sed('-i', /__ACTIVITY__/, safe_activity_name, activity_path);
|
||||||
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, 'res', 'values', 'strings.xml'));
|
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, 'res', 'values', 'strings.xml'));
|
||||||
|
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, '.project'));
|
||||||
shell.sed('-i', /__ID__/, package_name, activity_path);
|
shell.sed('-i', /__ID__/, package_name, activity_path);
|
||||||
|
|
||||||
shell.cp('-f', path.join(project_template_dir, 'AndroidManifest.xml'), manifest_path);
|
shell.cp('-f', path.join(project_template_dir, 'AndroidManifest.xml'), manifest_path);
|
||||||
@@ -179,6 +201,7 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
|||||||
shell.sed('-i', /__PACKAGE__/, package_name, manifest_path);
|
shell.sed('-i', /__PACKAGE__/, package_name, manifest_path);
|
||||||
shell.sed('-i', /__APILEVEL__/, target_api.split('-')[1], manifest_path);
|
shell.sed('-i', /__APILEVEL__/, target_api.split('-')[1], manifest_path);
|
||||||
copyScripts(project_path);
|
copyScripts(project_path);
|
||||||
|
copyAntRules(project_path);
|
||||||
});
|
});
|
||||||
// Link it to local android install.
|
// Link it to local android install.
|
||||||
return runAndroidUpdate(project_path, target_api, use_shared_project);
|
return runAndroidUpdate(project_path, target_api, use_shared_project);
|
||||||
@@ -187,18 +210,38 @@ exports.createProject = function(project_path, package_name, project_name, proje
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attribute removed in Cordova 4.4 (CB-5447).
|
||||||
|
function removeDebuggableFromManifest(projectPath) {
|
||||||
|
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||||
|
shell.sed('-i', /\s*android:debuggable="true"/, '', manifestPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractProjectNameFromManifest(projectPath) {
|
||||||
|
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||||
|
var manifestData = fs.readFileSync(manifestPath, 'utf8');
|
||||||
|
var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData);
|
||||||
|
if (!m) {
|
||||||
|
throw new Error('Could not find activity name in ' + manifestPath);
|
||||||
|
}
|
||||||
|
return m[1];
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a promise.
|
// Returns a promise.
|
||||||
exports.updateProject = function(projectPath) {
|
exports.updateProject = function(projectPath) {
|
||||||
|
var version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
||||||
// Check that requirements are met and proper targets are installed
|
// Check that requirements are met and proper targets are installed
|
||||||
return check_reqs.run()
|
return check_reqs.run()
|
||||||
.then(function() {
|
.then(function() {
|
||||||
var version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
|
var projectName = extractProjectNameFromManifest(projectPath);
|
||||||
var target_api = check_reqs.get_target();
|
var target_api = check_reqs.get_target();
|
||||||
copyJsAndLibrary(projectPath, false, null);
|
copyJsAndLibrary(projectPath, false, projectName);
|
||||||
copyScripts(projectPath);
|
copyScripts(projectPath);
|
||||||
|
copyAntRules(projectPath);
|
||||||
|
removeDebuggableFromManifest(projectPath);
|
||||||
return runAndroidUpdate(projectPath, target_api, false)
|
return runAndroidUpdate(projectPath, target_api, false)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
console.log('Android project is now at version ' + version);
|
console.log('Android project is now at version ' + version);
|
||||||
|
console.log('If you updated from a pre-3.2.0 version and use an IDE, we now require that you import the "CordovaLib" library project.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
23
bin/node_modules/which/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
5
bin/node_modules/which/README.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
The "which" util from npm's guts.
|
||||||
|
|
||||||
|
Finds the first instance of a specified executable in the PATH
|
||||||
|
environment variable. Does not cache the results, so `hash -r` is not
|
||||||
|
needed when the PATH changes.
|
||||||
14
bin/node_modules/which/bin/which
generated
vendored
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
var which = require("../")
|
||||||
|
if (process.argv.length < 3) {
|
||||||
|
console.error("Usage: which <thing>")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
which(process.argv[2], function (er, thing) {
|
||||||
|
if (er) {
|
||||||
|
console.error(er.message)
|
||||||
|
process.exit(er.errno || 127)
|
||||||
|
}
|
||||||
|
console.log(thing)
|
||||||
|
})
|
||||||
31
bin/node_modules/which/package.json
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"author": {
|
||||||
|
"name": "Isaac Z. Schlueter",
|
||||||
|
"email": "i@izs.me",
|
||||||
|
"url": "http://blog.izs.me"
|
||||||
|
},
|
||||||
|
"name": "which",
|
||||||
|
"description": "Like which(1) unix command. Find the first instance of an executable in the PATH.",
|
||||||
|
"version": "1.0.5",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/isaacs/node-which.git"
|
||||||
|
},
|
||||||
|
"main": "which.js",
|
||||||
|
"bin": {
|
||||||
|
"which": "./bin/which"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {},
|
||||||
|
"readme": "The \"which\" util from npm's guts.\n\nFinds the first instance of a specified executable in the PATH\nenvironment variable. Does not cache the results, so `hash -r` is not\nneeded when the PATH changes.\n",
|
||||||
|
"readmeFilename": "README.md",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/isaacs/node-which/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/isaacs/node-which",
|
||||||
|
"_id": "which@1.0.5",
|
||||||
|
"_from": "which@^1.0.5"
|
||||||
|
}
|
||||||
104
bin/node_modules/which/which.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
module.exports = which
|
||||||
|
which.sync = whichSync
|
||||||
|
|
||||||
|
var path = require("path")
|
||||||
|
, fs
|
||||||
|
, COLON = process.platform === "win32" ? ";" : ":"
|
||||||
|
, isExe
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs = require("graceful-fs")
|
||||||
|
} catch (ex) {
|
||||||
|
fs = require("fs")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform == "win32") {
|
||||||
|
// On windows, there is no good way to check that a file is executable
|
||||||
|
isExe = function isExe () { return true }
|
||||||
|
} else {
|
||||||
|
isExe = function isExe (mod, uid, gid) {
|
||||||
|
//console.error(mod, uid, gid);
|
||||||
|
//console.error("isExe?", (mod & 0111).toString(8))
|
||||||
|
var ret = (mod & 0001)
|
||||||
|
|| (mod & 0010) && process.getgid && gid === process.getgid()
|
||||||
|
|| (mod & 0100) && process.getuid && uid === process.getuid()
|
||||||
|
//console.error("isExe?", ret)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function which (cmd, cb) {
|
||||||
|
if (isAbsolute(cmd)) return cb(null, cmd)
|
||||||
|
var pathEnv = (process.env.PATH || "").split(COLON)
|
||||||
|
, pathExt = [""]
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
pathEnv.push(process.cwd())
|
||||||
|
pathExt = (process.env.PATHEXT || ".EXE").split(COLON)
|
||||||
|
if (cmd.indexOf(".") !== -1) pathExt.unshift("")
|
||||||
|
}
|
||||||
|
//console.error("pathEnv", pathEnv)
|
||||||
|
;(function F (i, l) {
|
||||||
|
if (i === l) return cb(new Error("not found: "+cmd))
|
||||||
|
var p = path.resolve(pathEnv[i], cmd)
|
||||||
|
;(function E (ii, ll) {
|
||||||
|
if (ii === ll) return F(i + 1, l)
|
||||||
|
var ext = pathExt[ii]
|
||||||
|
//console.error(p + ext)
|
||||||
|
fs.stat(p + ext, function (er, stat) {
|
||||||
|
if (!er &&
|
||||||
|
stat &&
|
||||||
|
stat.isFile() &&
|
||||||
|
isExe(stat.mode, stat.uid, stat.gid)) {
|
||||||
|
//console.error("yes, exe!", p + ext)
|
||||||
|
return cb(null, p + ext)
|
||||||
|
}
|
||||||
|
return E(ii + 1, ll)
|
||||||
|
})
|
||||||
|
})(0, pathExt.length)
|
||||||
|
})(0, pathEnv.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function whichSync (cmd) {
|
||||||
|
if (isAbsolute(cmd)) return cmd
|
||||||
|
var pathEnv = (process.env.PATH || "").split(COLON)
|
||||||
|
, pathExt = [""]
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
pathEnv.push(process.cwd())
|
||||||
|
pathExt = (process.env.PATHEXT || ".EXE").split(COLON)
|
||||||
|
if (cmd.indexOf(".") !== -1) pathExt.unshift("")
|
||||||
|
}
|
||||||
|
for (var i = 0, l = pathEnv.length; i < l; i ++) {
|
||||||
|
var p = path.join(pathEnv[i], cmd)
|
||||||
|
for (var j = 0, ll = pathExt.length; j < ll; j ++) {
|
||||||
|
var cur = p + pathExt[j]
|
||||||
|
var stat
|
||||||
|
try { stat = fs.statSync(cur) } catch (ex) {}
|
||||||
|
if (stat &&
|
||||||
|
stat.isFile() &&
|
||||||
|
isExe(stat.mode, stat.uid, stat.gid)) return cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("not found: "+cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isAbsolute = process.platform === "win32" ? absWin : absUnix
|
||||||
|
|
||||||
|
function absWin (p) {
|
||||||
|
if (absUnix(p)) return true
|
||||||
|
// pull off the device/UNC bit from a windows path.
|
||||||
|
// from node's lib/path.js
|
||||||
|
var splitDeviceRe =
|
||||||
|
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?/
|
||||||
|
, result = splitDeviceRe.exec(p)
|
||||||
|
, device = result[1] || ''
|
||||||
|
, isUnc = device && device.charAt(1) !== ':'
|
||||||
|
, isAbsolute = !!result[2] || isUnc // UNC paths are always absolute
|
||||||
|
|
||||||
|
return isAbsolute
|
||||||
|
}
|
||||||
|
|
||||||
|
function absUnix (p) {
|
||||||
|
return p.charAt(0) === "/" || p === ""
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "cordova-android",
|
|
||||||
"description": "Cordova tooling for the android platform.",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"homepage": "http://github.com/apache/cordova-android",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git-wip-us.apache.org/repos/asf/cordova-android.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"cli",
|
|
||||||
"cordova",
|
|
||||||
"tooling"
|
|
||||||
],
|
|
||||||
"engineStrict": "true",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"shelljs" : "0.2.6",
|
|
||||||
"q": "~0.9"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"name": "Benn Mapes",
|
|
||||||
"email": "bennmapes@gmail.com"
|
|
||||||
},
|
|
||||||
"contributors": [
|
|
||||||
]
|
|
||||||
}
|
|
||||||
32
bin/templates/cordova/defaults.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you 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.
|
||||||
|
-->
|
||||||
|
<widget xmlns = "http://www.w3.org/ns/widgets"
|
||||||
|
id = "io.cordova.helloCordova"
|
||||||
|
version = "2.0.0">
|
||||||
|
|
||||||
|
<!-- Preferences for Android -->
|
||||||
|
<preference name="loglevel" value="DEBUG" />
|
||||||
|
<preference name="AndroidLaunchMode" value="singleTop" />
|
||||||
|
|
||||||
|
<!-- This is required for native Android hooks -->
|
||||||
|
<feature name="App">
|
||||||
|
<param name="android-package" value="org.apache.cordova.App" />
|
||||||
|
</feature>
|
||||||
|
</widget>
|
||||||
77
bin/templates/cordova/lib/build.js
vendored
@@ -20,13 +20,32 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var shell = require('shelljs'),
|
var shell = require('shelljs'),
|
||||||
exec = require('./exec'),
|
spawn = require('./spawn'),
|
||||||
Q = require('q'),
|
Q = require('q'),
|
||||||
clean = require('./clean'),
|
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
fs = require('fs'),
|
fs = require('fs'),
|
||||||
|
which = require('which'),
|
||||||
ROOT = path.join(__dirname, '..', '..');
|
ROOT = path.join(__dirname, '..', '..');
|
||||||
|
|
||||||
|
|
||||||
|
function hasCustomRules() {
|
||||||
|
return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
|
||||||
|
}
|
||||||
|
module.exports.getAntArgs = function(cmd) {
|
||||||
|
var args = [cmd, '-f', path.join(ROOT, 'build.xml')];
|
||||||
|
try {
|
||||||
|
// Specify sdk dir in case local properties are missing
|
||||||
|
args.push('-Dsdk.dir='+path.join(which.sync('android'), '../..'));
|
||||||
|
} catch(e) {
|
||||||
|
// Can't find android; don't push arg: assume all is okay
|
||||||
|
}
|
||||||
|
// custom_rules.xml is required for incremental builds.
|
||||||
|
if (hasCustomRules()) {
|
||||||
|
args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Builds the project with ant.
|
* Builds the project with ant.
|
||||||
* Returns a promise.
|
* Returns a promise.
|
||||||
@@ -34,13 +53,12 @@ var shell = require('shelljs'),
|
|||||||
module.exports.run = function(build_type) {
|
module.exports.run = function(build_type) {
|
||||||
//default build type
|
//default build type
|
||||||
build_type = typeof build_type !== 'undefined' ? build_type : "--debug";
|
build_type = typeof build_type !== 'undefined' ? build_type : "--debug";
|
||||||
var cmd;
|
var args = module.exports.getAntArgs('debug');
|
||||||
switch(build_type) {
|
switch(build_type) {
|
||||||
case '--debug' :
|
case '--debug' :
|
||||||
cmd = 'ant debug -f "' + path.join(ROOT, 'build.xml') + '"';
|
|
||||||
break;
|
break;
|
||||||
case '--release' :
|
case '--release' :
|
||||||
cmd = 'ant release -f "' + path.join(ROOT, 'build.xml') + '"';
|
args[0] = 'release';
|
||||||
break;
|
break;
|
||||||
case '--nobuild' :
|
case '--nobuild' :
|
||||||
console.log('Skipping build...');
|
console.log('Skipping build...');
|
||||||
@@ -48,13 +66,14 @@ module.exports.run = function(build_type) {
|
|||||||
default :
|
default :
|
||||||
return Q.reject('Build option \'' + build_type + '\' not recognized.');
|
return Q.reject('Build option \'' + build_type + '\' not recognized.');
|
||||||
}
|
}
|
||||||
if(cmd) {
|
// Without our custom_rules.xml, we need to clean before building.
|
||||||
return clean.run() // TODO: Can we stop cleaning every time and let ant build incrementally?
|
var ret = Q();
|
||||||
.then(function() {
|
if (!hasCustomRules()) {
|
||||||
return exec(cmd);
|
ret = require('./clean').run();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return Q();
|
return ret.then(function() {
|
||||||
|
return spawn('ant', args);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -62,23 +81,37 @@ module.exports.run = function(build_type) {
|
|||||||
* the script will error out. (should we error or just return undefined?)
|
* the script will error out. (should we error or just return undefined?)
|
||||||
*/
|
*/
|
||||||
module.exports.get_apk = function() {
|
module.exports.get_apk = function() {
|
||||||
if(fs.existsSync(path.join(ROOT, 'bin'))) {
|
var binDir = '';
|
||||||
var bin_files = fs.readdirSync(path.join(ROOT, 'bin'));
|
if(!hasCustomRules()) {
|
||||||
for (file in bin_files) {
|
binDir = path.join(ROOT, 'bin');
|
||||||
if(path.extname(bin_files[file]) == '.apk') {
|
|
||||||
return path.join(ROOT, 'bin', bin_files[file]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.error('ERROR : No .apk found in \'bin\' folder');
|
|
||||||
process.exit(2);
|
|
||||||
} else {
|
} else {
|
||||||
console.error('ERROR : unable to find project bin folder, could not locate .apk');
|
binDir = path.join(ROOT, 'ant-build');
|
||||||
|
}
|
||||||
|
if (fs.existsSync(binDir)) {
|
||||||
|
var candidates = fs.readdirSync(binDir).filter(function(p) {
|
||||||
|
// Need to choose between release and debug .apk.
|
||||||
|
return path.extname(p) == '.apk';
|
||||||
|
}).map(function(p) {
|
||||||
|
p = path.join(binDir, p);
|
||||||
|
return { p: p, t: fs.statSync(p).mtime };
|
||||||
|
}).sort(function(a,b) {
|
||||||
|
return a.t > b.t ? -1 :
|
||||||
|
a.t < b.t ? 1 : 0;
|
||||||
|
});
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
console.error('ERROR : No .apk found in ' + binDir + ' directory');
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
console.log('Using apk: ' + candidates[0].p);
|
||||||
|
return candidates[0].p;
|
||||||
|
} else {
|
||||||
|
console.error('ERROR : unable to find project ' + binDir + ' directory, could not locate .apk');
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.help = function() {
|
module.exports.help = function() {
|
||||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'corodva', 'build')) + ' [build_type]');
|
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [build_type]');
|
||||||
console.log('Build Types : ');
|
console.log('Build Types : ');
|
||||||
console.log(' \'--debug\': Default build, will build project in using ant debug');
|
console.log(' \'--debug\': Default build, will build project in using ant debug');
|
||||||
console.log(' \'--release\': will build project using ant release');
|
console.log(' \'--release\': will build project using ant release');
|
||||||
|
|||||||
9
bin/templates/cordova/lib/clean.js
vendored
@@ -19,16 +19,17 @@
|
|||||||
under the License.
|
under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var exec = require('./exec'),
|
var build = require('./build'),
|
||||||
path = require('path'),
|
spawn = require('./spawn'),
|
||||||
ROOT = path.join(__dirname, '..', '..');
|
path = require('path');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Cleans the project using ant
|
* Cleans the project using ant
|
||||||
* Returns a promise.
|
* Returns a promise.
|
||||||
*/
|
*/
|
||||||
module.exports.run = function() {
|
module.exports.run = function() {
|
||||||
return exec('ant clean -f "' + path.join(ROOT, 'build.xml') + '"');
|
var args = build.getAntArgs('clean');
|
||||||
|
return spawn('ant', args);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.help = function() {
|
module.exports.help = function() {
|
||||||
|
|||||||
4
bin/templates/cordova/lib/device.js
vendored
@@ -64,7 +64,7 @@ module.exports.install = function(target) {
|
|||||||
var apk_path = build.get_apk();
|
var apk_path = build.get_apk();
|
||||||
launchName = appinfo.getActivityName();
|
launchName = appinfo.getActivityName();
|
||||||
console.log('Installing app on device...');
|
console.log('Installing app on device...');
|
||||||
var cmd = 'adb -s ' + target + ' install -r ' + apk_path;
|
var cmd = 'adb -s ' + target + ' install -r "' + apk_path + '"';
|
||||||
return exec(cmd);
|
return exec(cmd);
|
||||||
}).then(function(output) {
|
}).then(function(output) {
|
||||||
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
|
if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output);
|
||||||
@@ -79,7 +79,7 @@ module.exports.install = function(target) {
|
|||||||
var cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
var cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
|
||||||
return exec(cmd);
|
return exec(cmd);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
console.log('LANCH SUCCESS');
|
console.log('LAUNCH SUCCESS');
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
return Q.reject('ERROR: Failed to launch application on device: ' + err);
|
return Q.reject('ERROR: Failed to launch application on device: ' + err);
|
||||||
});
|
});
|
||||||
|
|||||||
18
bin/templates/cordova/lib/emulator.js
vendored
@@ -143,8 +143,8 @@ module.exports.list_targets = function() {
|
|||||||
/*
|
/*
|
||||||
* Starts an emulator with the given ID,
|
* Starts an emulator with the given ID,
|
||||||
* and returns the started ID of that emulator.
|
* and returns the started ID of that emulator.
|
||||||
* If no ID is given it will used the first image availible,
|
* If no ID is given it will used the first image available,
|
||||||
* if no image is availible it will error out (maybe create one?).
|
* if no image is available it will error out (maybe create one?).
|
||||||
*
|
*
|
||||||
* Returns a promise.
|
* Returns a promise.
|
||||||
*/
|
*/
|
||||||
@@ -178,14 +178,8 @@ module.exports.start = function(emulator_ID) {
|
|||||||
return Q(emulator_ID);
|
return Q(emulator_ID);
|
||||||
}
|
}
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
var cmd, args;
|
var cmd = 'emulator';
|
||||||
if(process.platform == 'win32' || process.platform == 'win64') {
|
var args = ['-avd', emulator_ID];
|
||||||
cmd = '%comspec%';
|
|
||||||
args = ['/c', 'start', 'cmd', '/c', 'emulator', '-avd', emulator_ID];
|
|
||||||
} else {
|
|
||||||
cmd = 'emulator';
|
|
||||||
args = ['-avd', emulator_ID];
|
|
||||||
}
|
|
||||||
var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true });
|
var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true });
|
||||||
proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time.
|
proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time.
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
@@ -274,7 +268,7 @@ module.exports.create_image = function(name, target) {
|
|||||||
.then(function() {
|
.then(function() {
|
||||||
// TODO: This seems like another error case, even though it always happens.
|
// TODO: This seems like another error case, even though it always happens.
|
||||||
console.error('ERROR : Unable to create an avd emulator, no targets found.');
|
console.error('ERROR : Unable to create an avd emulator, no targets found.');
|
||||||
console.error('Please insure you have targets availible by runing the "android" command');
|
console.error('Please insure you have targets available by running the "android" command');
|
||||||
return Q.reject();
|
return Q.reject();
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
console.error('ERROR : Failed to create emulator image : ');
|
console.error('ERROR : Failed to create emulator image : ');
|
||||||
@@ -305,7 +299,7 @@ module.exports.install = function(target) {
|
|||||||
|
|
||||||
console.log('Installing app on emulator...');
|
console.log('Installing app on emulator...');
|
||||||
var apk_path = build.get_apk();
|
var apk_path = build.get_apk();
|
||||||
return exec('adb -s ' + target + ' install -r ' + apk_path);
|
return exec('adb -s ' + target + ' install -r "' + apk_path + '"');
|
||||||
}).then(function(output) {
|
}).then(function(output) {
|
||||||
if (output.match(/Failure/)) {
|
if (output.match(/Failure/)) {
|
||||||
return Q.reject('Failed to install apk to emulator: ' + output);
|
return Q.reject('Failed to install apk to emulator: ' + output);
|
||||||
|
|||||||
4
bin/templates/cordova/lib/exec.js
vendored
@@ -27,10 +27,8 @@ var child_process = require('child_process'),
|
|||||||
// rejects with an error message and the stderr.
|
// rejects with an error message and the stderr.
|
||||||
module.exports = function(cmd, opt_cwd) {
|
module.exports = function(cmd, opt_cwd) {
|
||||||
var d = Q.defer();
|
var d = Q.defer();
|
||||||
console.log('exec: ' + cmd);
|
|
||||||
try {
|
try {
|
||||||
child_process.exec(cmd, {cwd: opt_cwd}, function(err, stdout, stderr) {
|
child_process.exec(cmd, {cwd: opt_cwd, maxBuffer: 1024000}, function(err, stdout, stderr) {
|
||||||
console.log([cmd, err, stdout, stderr]);
|
|
||||||
if (err) d.reject('Error executing "' + cmd + '": ' + stderr);
|
if (err) d.reject('Error executing "' + cmd + '": ' + stderr);
|
||||||
else d.resolve(stdout);
|
else d.resolve(stdout);
|
||||||
});
|
});
|
||||||
|
|||||||
2
bin/templates/cordova/lib/log.js
vendored
@@ -51,7 +51,7 @@ module.exports.run = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports.help = function() {
|
module.exports.help = function() {
|
||||||
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'corodva', 'log')));
|
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'log')));
|
||||||
console.log('Gives the logcat output on the command line.');
|
console.log('Gives the logcat output on the command line.');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|||||||
4
bin/templates/cordova/lib/run.js
vendored
@@ -26,7 +26,7 @@ var path = require('path'),
|
|||||||
Q = require('q');
|
Q = require('q');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Runs the application on a device if availible.
|
* Runs the application on a device if available.
|
||||||
* If not device is found, it will use a started emulator.
|
* If not device is found, it will use a started emulator.
|
||||||
* If no started emulators are found it will attempt to start an avd.
|
* If no started emulators are found it will attempt to start an avd.
|
||||||
* If no avds are found it will error out.
|
* If no avds are found it will error out.
|
||||||
@@ -91,7 +91,7 @@ var path = require('path'),
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// no target given, deploy to device if availible, otherwise use the emulator.
|
// no target given, deploy to device if available, otherwise use the emulator.
|
||||||
return device.list()
|
return device.list()
|
||||||
.then(function(device_list) {
|
.then(function(device_list) {
|
||||||
if (device_list.length > 0) {
|
if (device_list.length > 0) {
|
||||||
|
|||||||
49
bin/templates/cordova/lib/spawn.js
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/*
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var child_process = require('child_process'),
|
||||||
|
Q = require('q');
|
||||||
|
var isWindows = process.platform.slice(0, 3) == 'win';
|
||||||
|
|
||||||
|
// Takes a command and optional current working directory.
|
||||||
|
module.exports = function(cmd, args, opt_cwd) {
|
||||||
|
var d = Q.defer();
|
||||||
|
try {
|
||||||
|
// Work around spawn not being able to find .bat files.
|
||||||
|
if (isWindows) {
|
||||||
|
args.unshift('/s', '/c', cmd);
|
||||||
|
cmd = 'cmd';
|
||||||
|
}
|
||||||
|
var child = child_process.spawn(cmd, args, {cwd: opt_cwd, stdio: 'inherit'});
|
||||||
|
child.on('exit', function(code) {
|
||||||
|
if (code) {
|
||||||
|
d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args);
|
||||||
|
} else {
|
||||||
|
d.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
console.error('error caught: ' + e);
|
||||||
|
d.reject(e);
|
||||||
|
}
|
||||||
|
return d.promise;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -20,6 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Coho updates this line:
|
// Coho updates this line:
|
||||||
var VERSION = "3.2.0-dev";
|
var VERSION = "3.6.0-dev";
|
||||||
|
|
||||||
console.log(VERSION);
|
console.log(VERSION);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class __ACTIVITY__ extends CordovaActivity
|
|||||||
super.init();
|
super.init();
|
||||||
// Set by <content src="index.html" /> in config.xml
|
// Set by <content src="index.html" /> in config.xml
|
||||||
super.loadUrl(Config.getStartUrl());
|
super.loadUrl(Config.getStartUrl());
|
||||||
//super.loadUrl("file:///android_asset/www/index.html")
|
//super.loadUrl("file:///android_asset/www/index.html");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,12 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
|
||||||
<application android:icon="@drawable/icon" android:label="@string/app_name"
|
<application android:icon="@drawable/icon" android:label="@string/app_name"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true">
|
||||||
android:debuggable="true">
|
<activity android:name="__ACTIVITY__" android:label="@string/app_name" android:launchMode="singleTop"
|
||||||
<activity android:name="__ACTIVITY__" android:label="@string/app_name"
|
|
||||||
android:theme="@android:style/Theme.Black.NoTitleBar"
|
android:theme="@android:style/Theme.Black.NoTitleBar"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ var app = {
|
|||||||
// deviceready Event Handler
|
// deviceready Event Handler
|
||||||
//
|
//
|
||||||
// The scope of 'this' is the event. In order to call the 'receivedEvent'
|
// The scope of 'this' is the event. In order to call the 'receivedEvent'
|
||||||
// function, we must explicity call 'app.receivedEvent(...);'
|
// function, we must explicitly call 'app.receivedEvent(...);'
|
||||||
onDeviceReady: function() {
|
onDeviceReady: function() {
|
||||||
app.receivedEvent('deviceready');
|
app.receivedEvent('deviceready');
|
||||||
},
|
},
|
||||||
|
|||||||
21
bin/templates/project/custom_rules.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project>
|
||||||
|
<target name="-pre-compile">
|
||||||
|
<!-- Fix library references due to bug in build.xml: See: https://groups.google.com/forum/#!topic/android-developers/0ivH-YqCjzg -->
|
||||||
|
<pathconvert property="fixedJarsPath" refid="project.all.jars.path">
|
||||||
|
<filtermapper>
|
||||||
|
<replacestring from="/bin/" to="/ant-build/"/>
|
||||||
|
<replacestring from="\bin\" to="\ant-build\"/>
|
||||||
|
</filtermapper>
|
||||||
|
</pathconvert>
|
||||||
|
<path id="project.all.jars.path">
|
||||||
|
<pathelement path="${fixedJarsPath}"/>
|
||||||
|
</path>
|
||||||
|
<echo message="Set jars path to: ${toString:project.all.jars.path}"/>
|
||||||
|
</target>
|
||||||
|
<target name="-post-build">
|
||||||
|
<move file="ant-build/AndroidManifest.xml" tofile="ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
|
||||||
|
<move file="CordovaLib/ant-build/AndroidManifest.xml" tofile="CordovaLib/ant-build/AndroidManifest.cordova.xml" failonerror="false" overwrite="true" />
|
||||||
|
</target>
|
||||||
|
</project>
|
||||||
|
|
||||||
45
bin/templates/project/eclipse-project
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>__NAME__</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
<filteredResources>
|
||||||
|
<filter>
|
||||||
|
<id>1388696068187</id>
|
||||||
|
<name></name>
|
||||||
|
<type>10</type>
|
||||||
|
<matcher>
|
||||||
|
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||||
|
<arguments>1.0-name-matches-false-true-CordovaLib|platform_www|cordova</arguments>
|
||||||
|
</matcher>
|
||||||
|
</filter>
|
||||||
|
</filteredResources>
|
||||||
|
</projectDescription>
|
||||||
|
|
||||||
71
bin/templates/project/eclipse-project-CLI
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>__NAME__</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
<linkedResources>
|
||||||
|
<link>
|
||||||
|
<name>config.xml</name>
|
||||||
|
<type>1</type>
|
||||||
|
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/config.xml</locationURI>
|
||||||
|
</link>
|
||||||
|
<link>
|
||||||
|
<name>www</name>
|
||||||
|
<type>2</type>
|
||||||
|
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/www</locationURI>
|
||||||
|
</link>
|
||||||
|
<link>
|
||||||
|
<name>merges</name>
|
||||||
|
<type>2</type>
|
||||||
|
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/merges</locationURI>
|
||||||
|
</link>
|
||||||
|
</linkedResources>
|
||||||
|
<filteredResources>
|
||||||
|
<filter>
|
||||||
|
<id>1390880034107</id>
|
||||||
|
<name></name>
|
||||||
|
<type>30</type>
|
||||||
|
<matcher>
|
||||||
|
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||||
|
<arguments>1.0-projectRelativePath-matches-false-true-^(build.xml|ant-gen|ant-build|custom_rules.xml|CordovaLib|platform_www|cordova)</arguments>
|
||||||
|
</matcher>
|
||||||
|
</filter>
|
||||||
|
<filter>
|
||||||
|
<id>1390880034108</id>
|
||||||
|
<name></name>
|
||||||
|
<type>30</type>
|
||||||
|
<matcher>
|
||||||
|
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||||
|
<arguments>1.0-projectRelativePath-matches-false-true-^(assets/www|res/xml/config.xml)</arguments>
|
||||||
|
</matcher>
|
||||||
|
</filter>
|
||||||
|
</filteredResources>
|
||||||
|
</projectDescription>
|
||||||
|
|
||||||
BIN
bin/templates/project/res/drawable-land-hdpi/screen.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
bin/templates/project/res/drawable-land-ldpi/screen.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
bin/templates/project/res/drawable-land-mdpi/screen.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
bin/templates/project/res/drawable-land-xhdpi/screen.png
Normal file
|
After Width: | Height: | Size: 478 KiB |
BIN
bin/templates/project/res/drawable-port-hdpi/screen.png
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
bin/templates/project/res/drawable-port-ldpi/screen.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
bin/templates/project/res/drawable-port-mdpi/screen.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
bin/templates/project/res/drawable-port-xhdpi/screen.png
Normal file
|
After Width: | Height: | Size: 493 KiB |
@@ -1,8 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||||
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||||
<classpathentry kind="src" path="src"/>
|
<classpathentry kind="src" path="src"/>
|
||||||
<classpathentry kind="src" path="gen"/>
|
<classpathentry kind="src" path="gen"/>
|
||||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||||
<classpathentry kind="output" path="bin/classes"/>
|
<classpathentry kind="output" path="bin/classes"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|||||||
4
framework/.settings/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||||
|
org.eclipse.jdt.core.compiler.source=1.6
|
||||||
350
framework/assets/www/cordova.js
vendored
@@ -1,5 +1,5 @@
|
|||||||
// Platform: android
|
// Platform: android
|
||||||
// 3.2.0-dev-5ad41a7
|
// 3.6.0-dev-7e845f3
|
||||||
/*
|
/*
|
||||||
Licensed to the Apache Software Foundation (ASF) under one
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
or more contributor license agreements. See the NOTICE file
|
or more contributor license agreements. See the NOTICE file
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
under the License.
|
under the License.
|
||||||
*/
|
*/
|
||||||
;(function() {
|
;(function() {
|
||||||
var CORDOVA_JS_BUILD_LABEL = '3.2.0-dev-5ad41a7';
|
var CORDOVA_JS_BUILD_LABEL = '3.6.0-dev-7e845f3';
|
||||||
// file: lib/scripts/require.js
|
// file: src/scripts/require.js
|
||||||
|
|
||||||
/*jshint -W079 */
|
/*jshint -W079 */
|
||||||
/*jshint -W020 */
|
/*jshint -W020 */
|
||||||
@@ -34,7 +34,7 @@ var require,
|
|||||||
requireStack = [],
|
requireStack = [],
|
||||||
// Map of module ID -> index into requireStack of modules currently being built.
|
// Map of module ID -> index into requireStack of modules currently being built.
|
||||||
inProgressModules = {},
|
inProgressModules = {},
|
||||||
SEPERATOR = ".";
|
SEPARATOR = ".";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ var require,
|
|||||||
var resultantId = id;
|
var resultantId = id;
|
||||||
//Its a relative path, so lop off the last portion and add the id (minus "./")
|
//Its a relative path, so lop off the last portion and add the id (minus "./")
|
||||||
if (id.charAt(0) === ".") {
|
if (id.charAt(0) === ".") {
|
||||||
resultantId = module.id.slice(0, module.id.lastIndexOf(SEPERATOR)) + SEPERATOR + id.slice(2);
|
resultantId = module.id.slice(0, module.id.lastIndexOf(SEPARATOR)) + SEPARATOR + id.slice(2);
|
||||||
}
|
}
|
||||||
return require(resultantId);
|
return require(resultantId);
|
||||||
};
|
};
|
||||||
@@ -98,7 +98,7 @@ if (typeof module === "object" && typeof require === "function") {
|
|||||||
module.exports.define = define;
|
module.exports.define = define;
|
||||||
}
|
}
|
||||||
|
|
||||||
// file: lib/cordova.js
|
// file: src/cordova.js
|
||||||
define("cordova", function(require, exports, module) {
|
define("cordova", function(require, exports, module) {
|
||||||
|
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ module.exports = cordova;
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/android/android/nativeapiprovider.js
|
// file: src/android/android/nativeapiprovider.js
|
||||||
define("cordova/android/nativeapiprovider", function(require, exports, module) {
|
define("cordova/android/nativeapiprovider", function(require, exports, module) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -339,7 +339,7 @@ module.exports = {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/android/android/promptbasednativeapi.js
|
// file: src/android/android/promptbasednativeapi.js
|
||||||
define("cordova/android/promptbasednativeapi", function(require, exports, module) {
|
define("cordova/android/promptbasednativeapi", function(require, exports, module) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -361,7 +361,7 @@ module.exports = {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/common/argscheck.js
|
// file: src/common/argscheck.js
|
||||||
define("cordova/argscheck", function(require, exports, module) {
|
define("cordova/argscheck", function(require, exports, module) {
|
||||||
|
|
||||||
var exec = require('cordova/exec');
|
var exec = require('cordova/exec');
|
||||||
@@ -427,7 +427,7 @@ moduleExports.enableChecks = true;
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/common/base64.js
|
// file: src/common/base64.js
|
||||||
define("cordova/base64", function(require, exports, module) {
|
define("cordova/base64", function(require, exports, module) {
|
||||||
|
|
||||||
var base64 = exports;
|
var base64 = exports;
|
||||||
@@ -437,6 +437,16 @@ base64.fromArrayBuffer = function(arrayBuffer) {
|
|||||||
return uint8ToBase64(array);
|
return uint8ToBase64(array);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
base64.toArrayBuffer = function(str) {
|
||||||
|
var decodedStr = typeof atob != 'undefined' ? atob(str) : new Buffer(str,'base64').toString('binary');
|
||||||
|
var arrayBuffer = new ArrayBuffer(decodedStr.length);
|
||||||
|
var array = new Uint8Array(arrayBuffer);
|
||||||
|
for (var i=0, len=decodedStr.length; i < len; i++) {
|
||||||
|
array[i] = decodedStr.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return arrayBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
/* This code is based on the performance tests at http://jsperf.com/b64tests
|
/* This code is based on the performance tests at http://jsperf.com/b64tests
|
||||||
@@ -483,7 +493,7 @@ function uint8ToBase64(rawData) {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/common/builder.js
|
// file: src/common/builder.js
|
||||||
define("cordova/builder", function(require, exports, module) {
|
define("cordova/builder", function(require, exports, module) {
|
||||||
|
|
||||||
var utils = require('cordova/utils');
|
var utils = require('cordova/utils');
|
||||||
@@ -552,7 +562,7 @@ function include(parent, objects, clobber, merge) {
|
|||||||
include(result, obj.children, clobber, merge);
|
include(result, obj.children, clobber, merge);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
utils.alert('Exception building cordova JS globals: ' + e + ' for key "' + key + '"');
|
utils.alert('Exception building Cordova JS globals: ' + e + ' for key "' + key + '"');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -596,7 +606,7 @@ exports.replaceHookForTesting = function() {};
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/common/channel.js
|
// file: src/common/channel.js
|
||||||
define("cordova/channel", function(require, exports, module) {
|
define("cordova/channel", function(require, exports, module) {
|
||||||
|
|
||||||
var utils = require('cordova/utils'),
|
var utils = require('cordova/utils'),
|
||||||
@@ -815,6 +825,7 @@ channel.createSticky('onNativeReady');
|
|||||||
channel.createSticky('onCordovaReady');
|
channel.createSticky('onCordovaReady');
|
||||||
|
|
||||||
// Event to indicate that all automatically loaded JS plugins are loaded and ready.
|
// Event to indicate that all automatically loaded JS plugins are loaded and ready.
|
||||||
|
// FIXME remove this
|
||||||
channel.createSticky('onPluginsReady');
|
channel.createSticky('onPluginsReady');
|
||||||
|
|
||||||
// Event to indicate that Cordova is ready
|
// Event to indicate that Cordova is ready
|
||||||
@@ -837,7 +848,7 @@ module.exports = channel;
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/android/exec.js
|
// file: src/android/exec.js
|
||||||
define("cordova/exec", function(require, exports, module) {
|
define("cordova/exec", function(require, exports, module) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -919,7 +930,7 @@ function androidExec(success, fail, service, action, args) {
|
|||||||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
|
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
androidExec.processMessages(messages);
|
androidExec.processMessages(messages, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -963,7 +974,6 @@ androidExec.nativeToJsModes = nativeToJsModes;
|
|||||||
|
|
||||||
androidExec.setJsToNativeBridgeMode = function(mode) {
|
androidExec.setJsToNativeBridgeMode = function(mode) {
|
||||||
if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
|
if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
|
||||||
console.log('Falling back on PROMPT mode since _cordovaNative is missing. Expected for Android 3.2 and lower only.');
|
|
||||||
mode = jsToNativeModes.PROMPT;
|
mode = jsToNativeModes.PROMPT;
|
||||||
}
|
}
|
||||||
nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
|
nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
|
||||||
@@ -1028,53 +1038,99 @@ function processMessage(message) {
|
|||||||
}
|
}
|
||||||
cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
|
cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
|
||||||
} else {
|
} else {
|
||||||
console.log("processMessage failed: invalid message:" + message);
|
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("processMessage failed: Message: " + message);
|
|
||||||
console.log("processMessage failed: Error: " + e);
|
console.log("processMessage failed: Error: " + e);
|
||||||
console.log("processMessage failed: Stack: " + e.stack);
|
console.log("processMessage failed: Stack: " + e.stack);
|
||||||
|
console.log("processMessage failed: Message: " + message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isProcessing = false;
|
||||||
|
|
||||||
// This is called from the NativeToJsMessageQueue.java.
|
// This is called from the NativeToJsMessageQueue.java.
|
||||||
androidExec.processMessages = function(messages) {
|
androidExec.processMessages = function(messages, opt_useTimeout) {
|
||||||
if (messages) {
|
if (messages) {
|
||||||
messagesFromNative.push(messages);
|
messagesFromNative.push(messages);
|
||||||
// Check for the reentrant case, and enqueue the message if that's the case.
|
}
|
||||||
if (messagesFromNative.length > 1) {
|
// Check for the reentrant case.
|
||||||
return;
|
if (isProcessing) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
if (opt_useTimeout) {
|
||||||
|
window.setTimeout(androidExec.processMessages, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isProcessing = true;
|
||||||
|
try {
|
||||||
|
// TODO: add setImmediate polyfill and process only one message at a time.
|
||||||
while (messagesFromNative.length) {
|
while (messagesFromNative.length) {
|
||||||
// Don't unshift until the end so that reentrancy can be detected.
|
var msg = popMessageFromQueue();
|
||||||
messages = messagesFromNative[0];
|
|
||||||
// The Java side can send a * message to indicate that it
|
// The Java side can send a * message to indicate that it
|
||||||
// still has messages waiting to be retrieved.
|
// still has messages waiting to be retrieved.
|
||||||
if (messages == '*') {
|
if (msg == '*' && messagesFromNative.length === 0) {
|
||||||
messagesFromNative.shift();
|
setTimeout(pollOnce, 0);
|
||||||
window.setTimeout(pollOnce, 0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
processMessage(msg);
|
||||||
var spaceIdx = messages.indexOf(' ');
|
|
||||||
var msgLen = +messages.slice(0, spaceIdx);
|
|
||||||
var message = messages.substr(spaceIdx + 1, msgLen);
|
|
||||||
messages = messages.slice(spaceIdx + msgLen + 1);
|
|
||||||
processMessage(message);
|
|
||||||
if (messages) {
|
|
||||||
messagesFromNative[0] = messages;
|
|
||||||
} else {
|
|
||||||
messagesFromNative.shift();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
isProcessing = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function popMessageFromQueue() {
|
||||||
|
var messageBatch = messagesFromNative.shift();
|
||||||
|
if (messageBatch == '*') {
|
||||||
|
return '*';
|
||||||
|
}
|
||||||
|
|
||||||
|
var spaceIdx = messageBatch.indexOf(' ');
|
||||||
|
var msgLen = +messageBatch.slice(0, spaceIdx);
|
||||||
|
var message = messageBatch.substr(spaceIdx + 1, msgLen);
|
||||||
|
messageBatch = messageBatch.slice(spaceIdx + msgLen + 1);
|
||||||
|
if (messageBatch) {
|
||||||
|
messagesFromNative.unshift(messageBatch);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = androidExec;
|
module.exports = androidExec;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/common/init.js
|
// file: src/common/exec/proxy.js
|
||||||
|
define("cordova/exec/proxy", function(require, exports, module) {
|
||||||
|
|
||||||
|
|
||||||
|
// internal map of proxy function
|
||||||
|
var CommandProxyMap = {};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
// example: cordova.commandProxy.add("Accelerometer",{getCurrentAcceleration: function(successCallback, errorCallback, options) {...},...);
|
||||||
|
add:function(id,proxyObj) {
|
||||||
|
console.log("adding proxy for " + id);
|
||||||
|
CommandProxyMap[id] = proxyObj;
|
||||||
|
return proxyObj;
|
||||||
|
},
|
||||||
|
|
||||||
|
// cordova.commandProxy.remove("Accelerometer");
|
||||||
|
remove:function(id) {
|
||||||
|
var proxy = CommandProxyMap[id];
|
||||||
|
delete CommandProxyMap[id];
|
||||||
|
CommandProxyMap[id] = null;
|
||||||
|
return proxy;
|
||||||
|
},
|
||||||
|
|
||||||
|
get:function(service,action) {
|
||||||
|
return ( CommandProxyMap[service] ? CommandProxyMap[service][action] : null );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// file: src/common/init.js
|
||||||
define("cordova/init", function(require, exports, module) {
|
define("cordova/init", function(require, exports, module) {
|
||||||
|
|
||||||
var channel = require('cordova/channel');
|
var channel = require('cordova/channel');
|
||||||
@@ -1161,9 +1217,13 @@ modulemapper.clobbers('cordova/exec', 'Cordova.exec');
|
|||||||
// Call the platform-specific initialization.
|
// Call the platform-specific initialization.
|
||||||
platform.bootstrap && platform.bootstrap();
|
platform.bootstrap && platform.bootstrap();
|
||||||
|
|
||||||
pluginloader.load(function() {
|
// Wrap in a setTimeout to support the use-case of having plugin JS appended to cordova.js.
|
||||||
channel.onPluginsReady.fire();
|
// The delay allows the attached modules to be defined before the plugin loader looks for them.
|
||||||
});
|
setTimeout(function() {
|
||||||
|
pluginloader.load(function() {
|
||||||
|
channel.onPluginsReady.fire();
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create all cordova objects once native side is ready.
|
* Create all cordova objects once native side is ready.
|
||||||
@@ -1188,7 +1248,112 @@ channel.join(function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/common/modulemapper.js
|
// file: src/common/init_b.js
|
||||||
|
define("cordova/init_b", function(require, exports, module) {
|
||||||
|
|
||||||
|
var channel = require('cordova/channel');
|
||||||
|
var cordova = require('cordova');
|
||||||
|
var platform = require('cordova/platform');
|
||||||
|
|
||||||
|
var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady];
|
||||||
|
|
||||||
|
// setting exec
|
||||||
|
cordova.exec = require('cordova/exec');
|
||||||
|
|
||||||
|
function logUnfiredChannels(arr) {
|
||||||
|
for (var i = 0; i < arr.length; ++i) {
|
||||||
|
if (arr[i].state != 2) {
|
||||||
|
console.log('Channel not fired: ' + arr[i].type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setTimeout(function() {
|
||||||
|
if (channel.onDeviceReady.state != 2) {
|
||||||
|
console.log('deviceready has not fired after 5 seconds.');
|
||||||
|
logUnfiredChannels(platformInitChannelsArray);
|
||||||
|
logUnfiredChannels(channel.deviceReadyChannelsArray);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
// Replace navigator before any modules are required(), to ensure it happens as soon as possible.
|
||||||
|
// We replace it so that properties that can't be clobbered can instead be overridden.
|
||||||
|
function replaceNavigator(origNavigator) {
|
||||||
|
var CordovaNavigator = function() {};
|
||||||
|
CordovaNavigator.prototype = origNavigator;
|
||||||
|
var newNavigator = new CordovaNavigator();
|
||||||
|
// This work-around really only applies to new APIs that are newer than Function.bind.
|
||||||
|
// Without it, APIs such as getGamepads() break.
|
||||||
|
if (CordovaNavigator.bind) {
|
||||||
|
for (var key in origNavigator) {
|
||||||
|
if (typeof origNavigator[key] == 'function') {
|
||||||
|
newNavigator[key] = origNavigator[key].bind(origNavigator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newNavigator;
|
||||||
|
}
|
||||||
|
if (window.navigator) {
|
||||||
|
window.navigator = replaceNavigator(window.navigator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.console) {
|
||||||
|
window.console = {
|
||||||
|
log: function(){}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!window.console.warn) {
|
||||||
|
window.console.warn = function(msg) {
|
||||||
|
this.log("warn: " + msg);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register pause, resume and deviceready channels as events on document.
|
||||||
|
channel.onPause = cordova.addDocumentEventHandler('pause');
|
||||||
|
channel.onResume = cordova.addDocumentEventHandler('resume');
|
||||||
|
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
|
||||||
|
|
||||||
|
// Listen for DOMContentLoaded and notify our channel subscribers.
|
||||||
|
if (document.readyState == 'complete' || document.readyState == 'interactive') {
|
||||||
|
channel.onDOMContentLoaded.fire();
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
channel.onDOMContentLoaded.fire();
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// _nativeReady is global variable that the native side can set
|
||||||
|
// to signify that the native code is ready. It is a global since
|
||||||
|
// it may be called before any cordova JS is ready.
|
||||||
|
if (window._nativeReady) {
|
||||||
|
channel.onNativeReady.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the platform-specific initialization.
|
||||||
|
platform.bootstrap && platform.bootstrap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create all cordova objects once native side is ready.
|
||||||
|
*/
|
||||||
|
channel.join(function() {
|
||||||
|
|
||||||
|
platform.initialize && platform.initialize();
|
||||||
|
|
||||||
|
// Fire event to notify that all objects are created
|
||||||
|
channel.onCordovaReady.fire();
|
||||||
|
|
||||||
|
// Fire onDeviceReady event once page has fully loaded, all
|
||||||
|
// constructors have run and cordova info has been received from native
|
||||||
|
// side.
|
||||||
|
channel.join(function() {
|
||||||
|
require('cordova').fireDocumentEvent('deviceready');
|
||||||
|
}, channel.deviceReadyChannelsArray);
|
||||||
|
|
||||||
|
}, platformInitChannelsArray);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// file: src/common/modulemapper.js
|
||||||
define("cordova/modulemapper", function(require, exports, module) {
|
define("cordova/modulemapper", function(require, exports, module) {
|
||||||
|
|
||||||
var builder = require('cordova/builder'),
|
var builder = require('cordova/builder'),
|
||||||
@@ -1289,7 +1454,7 @@ exports.reset();
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/android/platform.js
|
// file: src/android/platform.js
|
||||||
define("cordova/platform", function(require, exports, module) {
|
define("cordova/platform", function(require, exports, module) {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -1330,7 +1495,7 @@ module.exports = {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/android/plugin/android/app.js
|
// file: src/android/plugin/android/app.js
|
||||||
define("cordova/plugin/android/app", function(require, exports, module) {
|
define("cordova/plugin/android/app", function(require, exports, module) {
|
||||||
|
|
||||||
var exec = require('cordova/exec');
|
var exec = require('cordova/exec');
|
||||||
@@ -1407,49 +1572,58 @@ module.exports = {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/common/pluginloader.js
|
// file: src/common/pluginloader.js
|
||||||
define("cordova/pluginloader", function(require, exports, module) {
|
define("cordova/pluginloader", function(require, exports, module) {
|
||||||
|
|
||||||
var modulemapper = require('cordova/modulemapper');
|
var modulemapper = require('cordova/modulemapper');
|
||||||
|
var urlutil = require('cordova/urlutil');
|
||||||
|
|
||||||
// Helper function to inject a <script> tag.
|
// Helper function to inject a <script> tag.
|
||||||
function injectScript(url, onload, onerror) {
|
// Exported for testing.
|
||||||
|
exports.injectScript = function(url, onload, onerror) {
|
||||||
var script = document.createElement("script");
|
var script = document.createElement("script");
|
||||||
// onload fires even when script fails loads with an error.
|
// onload fires even when script fails loads with an error.
|
||||||
script.onload = onload;
|
script.onload = onload;
|
||||||
script.onerror = onerror || onload;
|
// onerror fires for malformed URLs.
|
||||||
|
script.onerror = onerror;
|
||||||
script.src = url;
|
script.src = url;
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
|
};
|
||||||
|
|
||||||
|
function injectIfNecessary(id, url, onload, onerror) {
|
||||||
|
onerror = onerror || onload;
|
||||||
|
if (id in define.moduleMap) {
|
||||||
|
onload();
|
||||||
|
} else {
|
||||||
|
exports.injectScript(url, function() {
|
||||||
|
if (id in define.moduleMap) {
|
||||||
|
onload();
|
||||||
|
} else {
|
||||||
|
onerror();
|
||||||
|
}
|
||||||
|
}, onerror);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onScriptLoadingComplete(moduleList, finishPluginLoading) {
|
function onScriptLoadingComplete(moduleList, finishPluginLoading) {
|
||||||
// Loop through all the plugins and then through their clobbers and merges.
|
// Loop through all the plugins and then through their clobbers and merges.
|
||||||
for (var i = 0, module; module = moduleList[i]; i++) {
|
for (var i = 0, module; module = moduleList[i]; i++) {
|
||||||
if (module) {
|
if (module.clobbers && module.clobbers.length) {
|
||||||
try {
|
for (var j = 0; j < module.clobbers.length; j++) {
|
||||||
if (module.clobbers && module.clobbers.length) {
|
modulemapper.clobbers(module.id, module.clobbers[j]);
|
||||||
for (var j = 0; j < module.clobbers.length; j++) {
|
|
||||||
modulemapper.clobbers(module.id, module.clobbers[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module.merges && module.merges.length) {
|
|
||||||
for (var k = 0; k < module.merges.length; k++) {
|
|
||||||
modulemapper.merges(module.id, module.merges[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, if runs is truthy we want to simply require() the module.
|
|
||||||
// This can be skipped if it had any merges or clobbers, though,
|
|
||||||
// since the mapper will already have required the module.
|
|
||||||
if (module.runs && !(module.clobbers && module.clobbers.length) && !(module.merges && module.merges.length)) {
|
|
||||||
modulemapper.runs(module.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch(err) {
|
}
|
||||||
// error with module, most likely clobbers, should we continue?
|
|
||||||
|
if (module.merges && module.merges.length) {
|
||||||
|
for (var k = 0; k < module.merges.length; k++) {
|
||||||
|
modulemapper.merges(module.id, module.merges[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally, if runs is truthy we want to simply require() the module.
|
||||||
|
if (module.runs) {
|
||||||
|
modulemapper.runs(module.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finishPluginLoading();
|
finishPluginLoading();
|
||||||
@@ -1474,29 +1648,16 @@ function handlePluginsObject(path, moduleList, finishPluginLoading) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < moduleList.length; i++) {
|
for (var i = 0; i < moduleList.length; i++) {
|
||||||
injectScript(path + moduleList[i].file, scriptLoadedCallback);
|
injectIfNecessary(moduleList[i].id, path + moduleList[i].file, scriptLoadedCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectPluginScript(pathPrefix, finishPluginLoading) {
|
|
||||||
injectScript(pathPrefix + 'cordova_plugins.js', function(){
|
|
||||||
try {
|
|
||||||
var moduleList = require("cordova/plugin_list");
|
|
||||||
handlePluginsObject(pathPrefix, moduleList, finishPluginLoading);
|
|
||||||
} catch (e) {
|
|
||||||
// Error loading cordova_plugins.js, file not found or something
|
|
||||||
// this is an acceptable error, pre-3.0.0, so we just move on.
|
|
||||||
finishPluginLoading();
|
|
||||||
}
|
|
||||||
}, finishPluginLoading); // also, add script load error handler for file not found
|
|
||||||
}
|
|
||||||
|
|
||||||
function findCordovaPath() {
|
function findCordovaPath() {
|
||||||
var path = null;
|
var path = null;
|
||||||
var scripts = document.getElementsByTagName('script');
|
var scripts = document.getElementsByTagName('script');
|
||||||
var term = 'cordova.js';
|
var term = 'cordova.js';
|
||||||
for (var n = scripts.length-1; n>-1; n--) {
|
for (var n = scripts.length-1; n>-1; n--) {
|
||||||
var src = scripts[n].src;
|
var src = scripts[n].src.replace(/\?.*$/, ''); // Strip any query param (CB-6007).
|
||||||
if (src.indexOf(term) == (src.length - term.length)) {
|
if (src.indexOf(term) == (src.length - term.length)) {
|
||||||
path = src.substring(0, src.length - term.length);
|
path = src.substring(0, src.length - term.length);
|
||||||
break;
|
break;
|
||||||
@@ -1514,30 +1675,33 @@ exports.load = function(callback) {
|
|||||||
console.log('Could not find cordova.js script tag. Plugin loading may fail.');
|
console.log('Could not find cordova.js script tag. Plugin loading may fail.');
|
||||||
pathPrefix = '';
|
pathPrefix = '';
|
||||||
}
|
}
|
||||||
injectPluginScript(pathPrefix, callback);
|
injectIfNecessary('cordova/plugin_list', pathPrefix + 'cordova_plugins.js', function() {
|
||||||
|
var moduleList = require("cordova/plugin_list");
|
||||||
|
handlePluginsObject(pathPrefix, moduleList, callback);
|
||||||
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/common/urlutil.js
|
// file: src/common/urlutil.js
|
||||||
define("cordova/urlutil", function(require, exports, module) {
|
define("cordova/urlutil", function(require, exports, module) {
|
||||||
|
|
||||||
var urlutil = exports;
|
|
||||||
var anchorEl = document.createElement('a');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For already absolute URLs, returns what is passed in.
|
* For already absolute URLs, returns what is passed in.
|
||||||
* For relative URLs, converts them to absolute ones.
|
* For relative URLs, converts them to absolute ones.
|
||||||
*/
|
*/
|
||||||
urlutil.makeAbsolute = function(url) {
|
exports.makeAbsolute = function makeAbsolute(url) {
|
||||||
|
var anchorEl = document.createElement('a');
|
||||||
anchorEl.href = url;
|
anchorEl.href = url;
|
||||||
return anchorEl.href;
|
return anchorEl.href;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// file: lib/common/utils.js
|
// file: src/common/utils.js
|
||||||
define("cordova/utils", function(require, exports, module) {
|
define("cordova/utils", function(require, exports, module) {
|
||||||
|
|
||||||
var utils = exports;
|
var utils = exports;
|
||||||
@@ -1708,7 +1872,7 @@ function UUIDcreatePart(length) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.cordova = require('cordova');
|
window.cordova = require('cordova');
|
||||||
// file: lib/scripts/bootstrap.js
|
// file: src/scripts/bootstrap.js
|
||||||
|
|
||||||
require('cordova/init');
|
require('cordova/init');
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,13 @@
|
|||||||
<target name="-post-compile">
|
<target name="-post-compile">
|
||||||
</target>
|
</target>
|
||||||
-->
|
-->
|
||||||
|
<target name="-pre-clean">
|
||||||
|
<!-- delete generated javadoc -->
|
||||||
|
<delete dir="javadoc-public" failonerror="false" />
|
||||||
|
<delete dir="javadoc-private" failonerror="false" />
|
||||||
|
<!-- delete generated jar -->
|
||||||
|
<delete file="cordova-${version}.jar" failonerror="false" />
|
||||||
|
</target>
|
||||||
|
|
||||||
<!-- Import the actual build file.
|
<!-- Import the actual build file.
|
||||||
|
|
||||||
@@ -120,7 +127,31 @@
|
|||||||
that includes all JavaScript code.
|
that includes all JavaScript code.
|
||||||
-->
|
-->
|
||||||
<target name="jar" depends="-compile">
|
<target name="jar" depends="-compile">
|
||||||
<jar jarfile="cordova-${version}.jar" basedir="bin/classes" excludes="org/apache/cordova/R.class,org/apache/cordova/R$*.class"/>
|
<jar
|
||||||
|
basedir="bin/classes"
|
||||||
|
excludes="org/apache/cordova/R.class,org/apache/cordova/R$*.class"
|
||||||
|
jarfile="cordova-${version}.jar" />
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="javadoc">
|
||||||
|
<delete dir="javadoc-public" failonerror="false" />
|
||||||
|
<javadoc
|
||||||
|
access="public"
|
||||||
|
destdir="javadoc-public"
|
||||||
|
classpath="${sdk.dir}/platforms/${target}/android.jar">
|
||||||
|
<packageset dir="src">
|
||||||
|
<include name="org/apache/cordova/**" />
|
||||||
|
</packageset>
|
||||||
|
</javadoc>
|
||||||
|
<delete dir="javadoc-private" failonerror="false" />
|
||||||
|
<javadoc
|
||||||
|
access="private"
|
||||||
|
destdir="javadoc-private"
|
||||||
|
classpath="${sdk.dir}/platforms/${target}/android.jar">
|
||||||
|
<packageset dir="src">
|
||||||
|
<include name="org/apache/cordova/**" />
|
||||||
|
</packageset>
|
||||||
|
</javadoc>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<!-- tests for Java files -->
|
<!-- tests for Java files -->
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
# Indicates whether an apk should be generated for each density.
|
# Indicates whether an apk should be generated for each density.
|
||||||
split.density=false
|
split.density=false
|
||||||
# Project target.
|
# Project target.
|
||||||
target=android-18
|
target=android-19
|
||||||
apk-configurations=
|
apk-configurations=
|
||||||
renderscript.opt.level=O0
|
renderscript.opt.level=O0
|
||||||
android.library=true
|
android.library=true
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
<content src="index.html" />
|
<content src="index.html" />
|
||||||
|
|
||||||
<preference name="loglevel" value="DEBUG" />
|
<preference name="loglevel" value="DEBUG" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<preference name="splashscreen" value="resourceName" />
|
<preference name="splashscreen" value="resourceName" />
|
||||||
<preference name="backgroundColor" value="0xFFF" />
|
<preference name="backgroundColor" value="0xFFF" />
|
||||||
|
|||||||
33
framework/src/com/squareup/okhttp/Address.java
Normal file → Executable file
@@ -15,8 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.okhttp;
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.internal.Util;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.List;
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
@@ -38,16 +40,23 @@ public final class Address {
|
|||||||
final int uriPort;
|
final int uriPort;
|
||||||
final SSLSocketFactory sslSocketFactory;
|
final SSLSocketFactory sslSocketFactory;
|
||||||
final HostnameVerifier hostnameVerifier;
|
final HostnameVerifier hostnameVerifier;
|
||||||
|
final OkAuthenticator authenticator;
|
||||||
|
final List<String> transports;
|
||||||
|
|
||||||
public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory,
|
public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory,
|
||||||
HostnameVerifier hostnameVerifier, Proxy proxy) throws UnknownHostException {
|
HostnameVerifier hostnameVerifier, OkAuthenticator authenticator, Proxy proxy,
|
||||||
|
List<String> transports) throws UnknownHostException {
|
||||||
if (uriHost == null) throw new NullPointerException("uriHost == null");
|
if (uriHost == null) throw new NullPointerException("uriHost == null");
|
||||||
if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort);
|
if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort);
|
||||||
|
if (authenticator == null) throw new IllegalArgumentException("authenticator == null");
|
||||||
|
if (transports == null) throw new IllegalArgumentException("transports == null");
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
this.uriHost = uriHost;
|
this.uriHost = uriHost;
|
||||||
this.uriPort = uriPort;
|
this.uriPort = uriPort;
|
||||||
this.sslSocketFactory = sslSocketFactory;
|
this.sslSocketFactory = sslSocketFactory;
|
||||||
this.hostnameVerifier = hostnameVerifier;
|
this.hostnameVerifier = hostnameVerifier;
|
||||||
|
this.authenticator = authenticator;
|
||||||
|
this.transports = Util.immutableList(transports);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the hostname of the origin server. */
|
/** Returns the hostname of the origin server. */
|
||||||
@@ -79,6 +88,22 @@ public final class Address {
|
|||||||
return hostnameVerifier;
|
return hostnameVerifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the client's authenticator. This method never returns null.
|
||||||
|
*/
|
||||||
|
public OkAuthenticator getAuthenticator() {
|
||||||
|
return authenticator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the client's transports. This method always returns a non-null list
|
||||||
|
* that contains "http/1.1", possibly among other transports.
|
||||||
|
*/
|
||||||
|
public List<String> getTransports() {
|
||||||
|
return transports;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns this address's explicitly-specified HTTP proxy, or null to
|
* Returns this address's explicitly-specified HTTP proxy, or null to
|
||||||
* delegate to the HTTP client's proxy selector.
|
* delegate to the HTTP client's proxy selector.
|
||||||
@@ -94,7 +119,9 @@ public final class Address {
|
|||||||
&& this.uriHost.equals(that.uriHost)
|
&& this.uriHost.equals(that.uriHost)
|
||||||
&& this.uriPort == that.uriPort
|
&& this.uriPort == that.uriPort
|
||||||
&& equal(this.sslSocketFactory, that.sslSocketFactory)
|
&& equal(this.sslSocketFactory, that.sslSocketFactory)
|
||||||
&& equal(this.hostnameVerifier, that.hostnameVerifier);
|
&& equal(this.hostnameVerifier, that.hostnameVerifier)
|
||||||
|
&& equal(this.authenticator, that.authenticator)
|
||||||
|
&& equal(this.transports, that.transports);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -105,7 +132,9 @@ public final class Address {
|
|||||||
result = 31 * result + uriPort;
|
result = 31 * result + uriPort;
|
||||||
result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
|
result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
|
||||||
result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
|
result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
|
||||||
|
result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0);
|
||||||
result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
|
result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
|
||||||
|
result = 31 * result + transports.hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
78
framework/src/com/squareup/okhttp/Connection.java
Normal file → Executable file
@@ -31,6 +31,7 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
@@ -92,24 +93,20 @@ public final class Connection implements Closeable {
|
|||||||
|
|
||||||
public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)
|
public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (connected) {
|
if (connected) throw new IllegalStateException("already connected");
|
||||||
throw new IllegalStateException("already connected");
|
|
||||||
}
|
|
||||||
connected = true;
|
|
||||||
socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
|
socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
|
||||||
socket.connect(route.inetSocketAddress, connectTimeout);
|
Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);
|
||||||
socket.setSoTimeout(readTimeout);
|
socket.setSoTimeout(readTimeout);
|
||||||
in = socket.getInputStream();
|
in = socket.getInputStream();
|
||||||
out = socket.getOutputStream();
|
out = socket.getOutputStream();
|
||||||
|
|
||||||
if (route.address.sslSocketFactory != null) {
|
if (route.address.sslSocketFactory != null) {
|
||||||
upgradeToTls(tunnelRequest);
|
upgradeToTls(tunnelRequest);
|
||||||
|
} else {
|
||||||
|
streamWrapper();
|
||||||
}
|
}
|
||||||
|
connected = true;
|
||||||
// Use MTU-sized buffers to send fewer packets.
|
|
||||||
int mtu = Platform.get().getMtu(socket);
|
|
||||||
in = new BufferedInputStream(in, mtu);
|
|
||||||
out = new BufferedOutputStream(out, mtu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,7 +131,8 @@ public final class Connection implements Closeable {
|
|||||||
platform.supportTlsIntolerantServer(sslSocket);
|
platform.supportTlsIntolerantServer(sslSocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.modernTls) {
|
boolean useNpn = route.modernTls && route.address.transports.contains("spdy/3");
|
||||||
|
if (useNpn) {
|
||||||
platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
|
platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,14 +146,15 @@ public final class Connection implements Closeable {
|
|||||||
|
|
||||||
out = sslSocket.getOutputStream();
|
out = sslSocket.getOutputStream();
|
||||||
in = sslSocket.getInputStream();
|
in = sslSocket.getInputStream();
|
||||||
|
streamWrapper();
|
||||||
|
|
||||||
byte[] selectedProtocol;
|
byte[] selectedProtocol;
|
||||||
if (route.modernTls
|
if (useNpn && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
|
||||||
&& (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
|
|
||||||
if (Arrays.equals(selectedProtocol, SPDY3)) {
|
if (Arrays.equals(selectedProtocol, SPDY3)) {
|
||||||
sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
|
sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
|
||||||
spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out)
|
spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out)
|
||||||
.build();
|
.build();
|
||||||
|
spdyConnection.sendConnectionHeader();
|
||||||
} else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
|
} else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1"));
|
"Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1"));
|
||||||
@@ -190,6 +189,39 @@ public final class Connection implements Closeable {
|
|||||||
return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown();
|
return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if we are confident that we can read data from this
|
||||||
|
* connection. This is more expensive and more accurate than {@link
|
||||||
|
* #isAlive()}; callers should check {@link #isAlive()} first.
|
||||||
|
*/
|
||||||
|
public boolean isReadable() {
|
||||||
|
if (!(in instanceof BufferedInputStream)) {
|
||||||
|
return true; // Optimistic.
|
||||||
|
}
|
||||||
|
if (isSpdy()) {
|
||||||
|
return true; // Optimistic. We can't test SPDY because its streams are in use.
|
||||||
|
}
|
||||||
|
BufferedInputStream bufferedInputStream = (BufferedInputStream) in;
|
||||||
|
try {
|
||||||
|
int readTimeout = socket.getSoTimeout();
|
||||||
|
try {
|
||||||
|
socket.setSoTimeout(1);
|
||||||
|
bufferedInputStream.mark(1);
|
||||||
|
if (bufferedInputStream.read() == -1) {
|
||||||
|
return false; // Stream is exhausted; socket is closed.
|
||||||
|
}
|
||||||
|
bufferedInputStream.reset();
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
socket.setSoTimeout(readTimeout);
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException ignored) {
|
||||||
|
return true; // Read timed out; socket is good.
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false; // Couldn't read; socket is closed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void resetIdleStartTime() {
|
public void resetIdleStartTime() {
|
||||||
if (spdyConnection != null) {
|
if (spdyConnection != null) {
|
||||||
throw new IllegalStateException("spdyConnection != null");
|
throw new IllegalStateException("spdyConnection != null");
|
||||||
@@ -207,7 +239,7 @@ public final class Connection implements Closeable {
|
|||||||
* {@code keepAliveDurationNs}.
|
* {@code keepAliveDurationNs}.
|
||||||
*/
|
*/
|
||||||
public boolean isExpired(long keepAliveDurationNs) {
|
public boolean isExpired(long keepAliveDurationNs) {
|
||||||
return isIdle() && System.nanoTime() - getIdleStartTimeNs() > keepAliveDurationNs;
|
return getIdleStartTimeNs() < System.nanoTime() - keepAliveDurationNs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,7 +252,8 @@ public final class Connection implements Closeable {
|
|||||||
|
|
||||||
/** Returns the transport appropriate for this connection. */
|
/** Returns the transport appropriate for this connection. */
|
||||||
public Object newTransport(HttpEngine httpEngine) throws IOException {
|
public Object newTransport(HttpEngine httpEngine) throws IOException {
|
||||||
return (spdyConnection != null) ? new SpdyTransport(httpEngine, spdyConnection)
|
return (spdyConnection != null)
|
||||||
|
? new SpdyTransport(httpEngine, spdyConnection)
|
||||||
: new HttpTransport(httpEngine, out, in);
|
: new HttpTransport(httpEngine, out, in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +291,11 @@ public final class Connection implements Closeable {
|
|||||||
return route.address.sslSocketFactory != null && route.proxy.type() == Proxy.Type.HTTP;
|
return route.address.sslSocketFactory != null && route.proxy.type() == Proxy.Type.HTTP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateReadTimeout(int newTimeout) throws IOException {
|
||||||
|
if (!connected) throw new IllegalStateException("updateReadTimeout - not connected");
|
||||||
|
socket.setSoTimeout(newTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To make an HTTPS connection over an HTTP proxy, send an unencrypted
|
* To make an HTTPS connection over an HTTP proxy, send an unencrypted
|
||||||
* CONNECT request to create the proxy connection. This may need to be
|
* CONNECT request to create the proxy connection. This may need to be
|
||||||
@@ -275,8 +313,9 @@ public final class Connection implements Closeable {
|
|||||||
case HTTP_PROXY_AUTH:
|
case HTTP_PROXY_AUTH:
|
||||||
requestHeaders = new RawHeaders(requestHeaders);
|
requestHeaders = new RawHeaders(requestHeaders);
|
||||||
URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/");
|
URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/");
|
||||||
boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH,
|
boolean credentialsFound = HttpAuthenticator.processAuthHeader(
|
||||||
responseHeaders, requestHeaders, route.proxy, url);
|
route.address.authenticator, HTTP_PROXY_AUTH, responseHeaders, requestHeaders,
|
||||||
|
route.proxy, url);
|
||||||
if (credentialsFound) {
|
if (credentialsFound) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
@@ -288,4 +327,9 @@ public final class Connection implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void streamWrapper() throws IOException {
|
||||||
|
in = new BufferedInputStream(in, 4096);
|
||||||
|
out = new BufferedOutputStream(out, 256);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
framework/src/com/squareup/okhttp/ConnectionPool.java
Normal file → Executable file
@@ -80,8 +80,9 @@ public class ConnectionPool {
|
|||||||
private final LinkedList<Connection> connections = new LinkedList<Connection>();
|
private final LinkedList<Connection> connections = new LinkedList<Connection>();
|
||||||
|
|
||||||
/** We use a single background thread to cleanup expired connections. */
|
/** We use a single background thread to cleanup expired connections. */
|
||||||
private final ExecutorService executorService =
|
private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
|
||||||
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
|
||||||
|
Util.daemonThreadFactory("OkHttp ConnectionPool"));
|
||||||
private final Callable<Void> connectionsCleanupCallable = new Callable<Void>() {
|
private final Callable<Void> connectionsCleanupCallable = new Callable<Void>() {
|
||||||
@Override public Void call() throws Exception {
|
@Override public Void call() throws Exception {
|
||||||
List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP);
|
List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP);
|
||||||
@@ -215,8 +216,6 @@ public class ConnectionPool {
|
|||||||
* <p>It is an error to use {@code connection} after calling this method.
|
* <p>It is an error to use {@code connection} after calling this method.
|
||||||
*/
|
*/
|
||||||
public void recycle(Connection connection) {
|
public void recycle(Connection connection) {
|
||||||
executorService.submit(connectionsCleanupCallable);
|
|
||||||
|
|
||||||
if (connection.isSpdy()) {
|
if (connection.isSpdy()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -239,6 +238,8 @@ public class ConnectionPool {
|
|||||||
connections.addFirst(connection);
|
connections.addFirst(connection);
|
||||||
connection.resetIdleStartTime();
|
connection.resetIdleStartTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executorService.submit(connectionsCleanupCallable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
86
framework/src/com/squareup/okhttp/Dispatcher.java
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.internal.http.ResponseHeaders;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
final class Dispatcher {
|
||||||
|
// TODO: thread pool size should be configurable; possibly configurable per host.
|
||||||
|
private final ThreadPoolExecutor executorService = new ThreadPoolExecutor(
|
||||||
|
8, 8, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
||||||
|
private final Map<Object, List<Job>> enqueuedJobs = new LinkedHashMap<Object, List<Job>>();
|
||||||
|
|
||||||
|
public synchronized void enqueue(
|
||||||
|
OkHttpClient client, Request request, Response.Receiver responseReceiver) {
|
||||||
|
Job job = new Job(this, client, request, responseReceiver);
|
||||||
|
List<Job> jobsForTag = enqueuedJobs.get(request.tag());
|
||||||
|
if (jobsForTag == null) {
|
||||||
|
jobsForTag = new ArrayList<Job>(2);
|
||||||
|
enqueuedJobs.put(request.tag(), jobsForTag);
|
||||||
|
}
|
||||||
|
jobsForTag.add(job);
|
||||||
|
executorService.execute(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void cancel(Object tag) {
|
||||||
|
List<Job> jobs = enqueuedJobs.remove(tag);
|
||||||
|
if (jobs == null) return;
|
||||||
|
for (Job job : jobs) {
|
||||||
|
executorService.remove(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void finished(Job job) {
|
||||||
|
List<Job> jobs = enqueuedJobs.get(job.tag());
|
||||||
|
if (jobs != null) jobs.remove(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class RealResponseBody extends Response.Body {
|
||||||
|
private final ResponseHeaders responseHeaders;
|
||||||
|
private final InputStream in;
|
||||||
|
|
||||||
|
RealResponseBody(ResponseHeaders responseHeaders, InputStream in) {
|
||||||
|
this.responseHeaders = responseHeaders;
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean ready() throws IOException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public MediaType contentType() {
|
||||||
|
String contentType = responseHeaders.getContentType();
|
||||||
|
return contentType != null ? MediaType.parse(contentType) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public long contentLength() {
|
||||||
|
return responseHeaders.getContentLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public InputStream byteStream() throws IOException {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
framework/src/com/squareup/okhttp/Failure.java
Executable file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A failure attempting to retrieve an HTTP response.
|
||||||
|
*
|
||||||
|
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||||
|
* This class is in beta. APIs are subject to change!
|
||||||
|
*/
|
||||||
|
/* OkHttp 2.0: public */ class Failure {
|
||||||
|
private final Request request;
|
||||||
|
private final Throwable exception;
|
||||||
|
|
||||||
|
private Failure(Builder builder) {
|
||||||
|
this.request = builder.request;
|
||||||
|
this.exception = builder.exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request request() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Throwable exception() {
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private Request request;
|
||||||
|
private Throwable exception;
|
||||||
|
|
||||||
|
public Builder request(Request request) {
|
||||||
|
this.request = request;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder exception(Throwable exception) {
|
||||||
|
this.exception = exception;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Failure build() {
|
||||||
|
return new Failure(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
framework/src/com/squareup/okhttp/HttpResponseCache.java
Normal file → Executable file
@@ -22,8 +22,8 @@ import com.squareup.okhttp.internal.StrictLineReader;
|
|||||||
import com.squareup.okhttp.internal.Util;
|
import com.squareup.okhttp.internal.Util;
|
||||||
import com.squareup.okhttp.internal.http.HttpEngine;
|
import com.squareup.okhttp.internal.http.HttpEngine;
|
||||||
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
|
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
|
||||||
|
import com.squareup.okhttp.internal.http.HttpsEngine;
|
||||||
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
|
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
|
||||||
import com.squareup.okhttp.internal.http.OkResponseCache;
|
|
||||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||||
import com.squareup.okhttp.internal.http.ResponseHeaders;
|
import com.squareup.okhttp.internal.http.ResponseHeaders;
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
@@ -35,7 +35,6 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.net.CacheRequest;
|
import java.net.CacheRequest;
|
||||||
import java.net.CacheResponse;
|
import java.net.CacheResponse;
|
||||||
@@ -44,8 +43,6 @@ import java.net.ResponseCache;
|
|||||||
import java.net.SecureCacheResponse;
|
import java.net.SecureCacheResponse;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateEncodingException;
|
import java.security.cert.CertificateEncodingException;
|
||||||
@@ -55,8 +52,8 @@ import java.security.cert.X509Certificate;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
|
||||||
import static com.squareup.okhttp.internal.Util.US_ASCII;
|
import static com.squareup.okhttp.internal.Util.US_ASCII;
|
||||||
import static com.squareup.okhttp.internal.Util.UTF_8;
|
import static com.squareup.okhttp.internal.Util.UTF_8;
|
||||||
@@ -119,9 +116,6 @@ import static com.squareup.okhttp.internal.Util.UTF_8;
|
|||||||
* }</pre>
|
* }</pre>
|
||||||
*/
|
*/
|
||||||
public final class HttpResponseCache extends ResponseCache {
|
public final class HttpResponseCache extends ResponseCache {
|
||||||
private static final char[] DIGITS =
|
|
||||||
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
|
||||||
|
|
||||||
// TODO: add APIs to iterate the cache?
|
// TODO: add APIs to iterate the cache?
|
||||||
private static final int VERSION = 201105;
|
private static final int VERSION = 201105;
|
||||||
private static final int ENTRY_METADATA = 0;
|
private static final int ENTRY_METADATA = 0;
|
||||||
@@ -153,6 +147,10 @@ public final class HttpResponseCache extends ResponseCache {
|
|||||||
return HttpResponseCache.this.put(uri, connection);
|
return HttpResponseCache.this.put(uri, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public void maybeRemove(String requestMethod, URI uri) throws IOException {
|
||||||
|
HttpResponseCache.this.maybeRemove(requestMethod, uri);
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void update(
|
@Override public void update(
|
||||||
CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException {
|
CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException {
|
||||||
HttpResponseCache.this.update(conditionalCacheHit, connection);
|
HttpResponseCache.this.update(conditionalCacheHit, connection);
|
||||||
@@ -172,26 +170,7 @@ public final class HttpResponseCache extends ResponseCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String uriToKey(URI uri) {
|
private String uriToKey(URI uri) {
|
||||||
try {
|
return Util.hash(uri.toString());
|
||||||
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
|
||||||
byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8"));
|
|
||||||
return bytesToHexString(md5bytes);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String bytesToHexString(byte[] bytes) {
|
|
||||||
char[] digits = DIGITS;
|
|
||||||
char[] buf = new char[bytes.length * 2];
|
|
||||||
int c = 0;
|
|
||||||
for (byte b : bytes) {
|
|
||||||
buf[c++] = digits[(b >> 4) & 0xf];
|
|
||||||
buf[c++] = digits[b & 0xf];
|
|
||||||
}
|
|
||||||
return new String(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public CacheResponse get(URI uri, String requestMethod,
|
@Override public CacheResponse get(URI uri, String requestMethod,
|
||||||
@@ -226,17 +205,11 @@ public final class HttpResponseCache extends ResponseCache {
|
|||||||
|
|
||||||
HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
|
HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
|
||||||
String requestMethod = httpConnection.getRequestMethod();
|
String requestMethod = httpConnection.getRequestMethod();
|
||||||
String key = uriToKey(uri);
|
|
||||||
|
|
||||||
if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals(
|
if (maybeRemove(requestMethod, uri)) {
|
||||||
"DELETE")) {
|
|
||||||
try {
|
|
||||||
cache.remove(key);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
// The cache cannot be written.
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
} else if (!requestMethod.equals("GET")) {
|
}
|
||||||
|
if (!requestMethod.equals("GET")) {
|
||||||
// Don't cache non-GET responses. We're technically allowed to cache
|
// Don't cache non-GET responses. We're technically allowed to cache
|
||||||
// HEAD requests and some POST requests, but the complexity of doing
|
// HEAD requests and some POST requests, but the complexity of doing
|
||||||
// so is high and the benefit is low.
|
// so is high and the benefit is low.
|
||||||
@@ -259,7 +232,7 @@ public final class HttpResponseCache extends ResponseCache {
|
|||||||
Entry entry = new Entry(uri, varyHeaders, httpConnection);
|
Entry entry = new Entry(uri, varyHeaders, httpConnection);
|
||||||
DiskLruCache.Editor editor = null;
|
DiskLruCache.Editor editor = null;
|
||||||
try {
|
try {
|
||||||
editor = cache.edit(key);
|
editor = cache.edit(uriToKey(uri));
|
||||||
if (editor == null) {
|
if (editor == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -271,6 +244,23 @@ public final class HttpResponseCache extends ResponseCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the supplied {@code requestMethod} potentially invalidates an entry in the
|
||||||
|
* cache.
|
||||||
|
*/
|
||||||
|
private boolean maybeRemove(String requestMethod, URI uri) {
|
||||||
|
if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals(
|
||||||
|
"DELETE")) {
|
||||||
|
try {
|
||||||
|
cache.remove(uriToKey(uri));
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
// The cache cannot be written.
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
|
private void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
HttpEngine httpEngine = getHttpEngine(httpConnection);
|
HttpEngine httpEngine = getHttpEngine(httpConnection);
|
||||||
@@ -331,6 +321,30 @@ public final class HttpResponseCache extends ResponseCache {
|
|||||||
return writeSuccessCount;
|
return writeSuccessCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getSize() {
|
||||||
|
return cache.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMaxSize() {
|
||||||
|
return cache.getMaxSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush() throws IOException {
|
||||||
|
cache.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
cache.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getDirectory() {
|
||||||
|
return cache.getDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return cache.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void trackResponse(ResponseSource source) {
|
private synchronized void trackResponse(ResponseSource source) {
|
||||||
requestCount++;
|
requestCount++;
|
||||||
|
|
||||||
@@ -383,8 +397,7 @@ public final class HttpResponseCache extends ResponseCache {
|
|||||||
editor.commit();
|
editor.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void write(byte[] buffer, int offset, int length) throws IOException {
|
||||||
public void write(byte[] buffer, int offset, int length) throws IOException {
|
|
||||||
// Since we don't override "write(int oneByte)", we can write directly to "out"
|
// Since we don't override "write(int oneByte)", we can write directly to "out"
|
||||||
// and avoid the inefficient implementation from the FilterOutputStream.
|
// and avoid the inefficient implementation from the FilterOutputStream.
|
||||||
out.write(buffer, offset, length);
|
out.write(buffer, offset, length);
|
||||||
@@ -513,16 +526,16 @@ public final class HttpResponseCache extends ResponseCache {
|
|||||||
this.requestMethod = httpConnection.getRequestMethod();
|
this.requestMethod = httpConnection.getRequestMethod();
|
||||||
this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true);
|
this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true);
|
||||||
|
|
||||||
if (isHttps()) {
|
SSLSocket sslSocket = getSslSocket(httpConnection);
|
||||||
HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
|
if (sslSocket != null) {
|
||||||
cipherSuite = httpsConnection.getCipherSuite();
|
cipherSuite = sslSocket.getSession().getCipherSuite();
|
||||||
Certificate[] peerCertificatesNonFinal = null;
|
Certificate[] peerCertificatesNonFinal = null;
|
||||||
try {
|
try {
|
||||||
peerCertificatesNonFinal = httpsConnection.getServerCertificates();
|
peerCertificatesNonFinal = sslSocket.getSession().getPeerCertificates();
|
||||||
} catch (SSLPeerUnverifiedException ignored) {
|
} catch (SSLPeerUnverifiedException ignored) {
|
||||||
}
|
}
|
||||||
peerCertificates = peerCertificatesNonFinal;
|
peerCertificates = peerCertificatesNonFinal;
|
||||||
localCertificates = httpsConnection.getLocalCertificates();
|
localCertificates = sslSocket.getSession().getLocalCertificates();
|
||||||
} else {
|
} else {
|
||||||
cipherSuite = null;
|
cipherSuite = null;
|
||||||
peerCertificates = null;
|
peerCertificates = null;
|
||||||
@@ -530,6 +543,22 @@ public final class HttpResponseCache extends ResponseCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the SSL socket used by {@code httpConnection} for HTTPS, nor null
|
||||||
|
* if the connection isn't using HTTPS. Since we permit redirects across
|
||||||
|
* protocols (HTTP to HTTPS or vice versa), the implementation type of the
|
||||||
|
* connection doesn't necessarily match the implementation type of its HTTP
|
||||||
|
* engine.
|
||||||
|
*/
|
||||||
|
private SSLSocket getSslSocket(HttpURLConnection httpConnection) {
|
||||||
|
HttpEngine engine = httpConnection instanceof HttpsURLConnectionImpl
|
||||||
|
? ((HttpsURLConnectionImpl) httpConnection).getHttpEngine()
|
||||||
|
: ((HttpURLConnectionImpl) httpConnection).getHttpEngine();
|
||||||
|
return engine instanceof HttpsEngine
|
||||||
|
? ((HttpsEngine) engine).getSslSocket()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
public void writeTo(DiskLruCache.Editor editor) throws IOException {
|
public void writeTo(DiskLruCache.Editor editor) throws IOException {
|
||||||
OutputStream out = editor.newOutputStream(ENTRY_METADATA);
|
OutputStream out = editor.newOutputStream(ENTRY_METADATA);
|
||||||
Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8));
|
Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8));
|
||||||
|
|||||||
232
framework/src/com/squareup/okhttp/Job.java
Executable file
@@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.internal.http.HttpAuthenticator;
|
||||||
|
import com.squareup.okhttp.internal.http.HttpEngine;
|
||||||
|
import com.squareup.okhttp.internal.http.HttpTransport;
|
||||||
|
import com.squareup.okhttp.internal.http.HttpsEngine;
|
||||||
|
import com.squareup.okhttp.internal.http.Policy;
|
||||||
|
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.ProtocolException;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
||||||
|
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_MOVED_PERM;
|
||||||
|
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_MOVED_TEMP;
|
||||||
|
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_MULT_CHOICE;
|
||||||
|
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_PROXY_AUTH;
|
||||||
|
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_SEE_OTHER;
|
||||||
|
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_TEMP_REDIRECT;
|
||||||
|
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_UNAUTHORIZED;
|
||||||
|
|
||||||
|
final class Job implements Runnable, Policy {
|
||||||
|
private final Dispatcher dispatcher;
|
||||||
|
private final OkHttpClient client;
|
||||||
|
private final Response.Receiver responseReceiver;
|
||||||
|
|
||||||
|
/** The request; possibly a consequence of redirects or auth headers. */
|
||||||
|
private Request request;
|
||||||
|
|
||||||
|
public Job(Dispatcher dispatcher, OkHttpClient client, Request request,
|
||||||
|
Response.Receiver responseReceiver) {
|
||||||
|
this.dispatcher = dispatcher;
|
||||||
|
this.client = client;
|
||||||
|
this.request = request;
|
||||||
|
this.responseReceiver = responseReceiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int getChunkLength() {
|
||||||
|
return request.body().contentLength() == -1 ? HttpTransport.DEFAULT_CHUNK_LENGTH : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public long getFixedContentLength() {
|
||||||
|
return request.body().contentLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean getUseCaches() {
|
||||||
|
return false; // TODO.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public HttpURLConnection getHttpConnectionToCache() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public URL getURL() {
|
||||||
|
return request.url();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public long getIfModifiedSince() {
|
||||||
|
return 0; // For HttpURLConnection only. We let the cache drive this.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean usingProxy() {
|
||||||
|
return false; // We let the connection decide this.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void setSelectedProxy(Proxy proxy) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
Object tag() {
|
||||||
|
return request.tag();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void run() {
|
||||||
|
try {
|
||||||
|
Response response = execute();
|
||||||
|
responseReceiver.onResponse(response);
|
||||||
|
} catch (IOException e) {
|
||||||
|
responseReceiver.onFailure(new Failure.Builder()
|
||||||
|
.request(request)
|
||||||
|
.exception(e)
|
||||||
|
.build());
|
||||||
|
} finally {
|
||||||
|
// TODO: close the response body
|
||||||
|
// TODO: release the HTTP engine (potentially multiple!)
|
||||||
|
dispatcher.finished(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response execute() throws IOException {
|
||||||
|
Connection connection = null;
|
||||||
|
Response redirectedBy = null;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
HttpEngine engine = newEngine(connection);
|
||||||
|
|
||||||
|
Request.Body body = request.body();
|
||||||
|
if (body != null) {
|
||||||
|
MediaType contentType = body.contentType();
|
||||||
|
if (contentType == null) throw new IllegalStateException("contentType == null");
|
||||||
|
if (engine.getRequestHeaders().getContentType() == null) {
|
||||||
|
engine.getRequestHeaders().setContentType(contentType.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.sendRequest();
|
||||||
|
|
||||||
|
if (body != null) {
|
||||||
|
body.writeTo(engine.getRequestBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.readResponse();
|
||||||
|
|
||||||
|
int responseCode = engine.getResponseCode();
|
||||||
|
Dispatcher.RealResponseBody responseBody = new Dispatcher.RealResponseBody(
|
||||||
|
engine.getResponseHeaders(), engine.getResponseBody());
|
||||||
|
|
||||||
|
Response response = new Response.Builder(request, responseCode)
|
||||||
|
.rawHeaders(engine.getResponseHeaders().getHeaders())
|
||||||
|
.body(responseBody)
|
||||||
|
.redirectedBy(redirectedBy)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Request redirect = processResponse(engine, response);
|
||||||
|
|
||||||
|
if (redirect == null) {
|
||||||
|
engine.automaticallyReleaseConnectionToPool();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fail if too many redirects
|
||||||
|
// TODO: fail if not following redirects
|
||||||
|
// TODO: release engine
|
||||||
|
|
||||||
|
connection = sameConnection(request, redirect) ? engine.getConnection() : null;
|
||||||
|
redirectedBy = response;
|
||||||
|
request = redirect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpEngine newEngine(Connection connection) throws IOException {
|
||||||
|
String protocol = request.url().getProtocol();
|
||||||
|
RawHeaders requestHeaders = request.rawHeaders();
|
||||||
|
if (protocol.equals("http")) {
|
||||||
|
return new HttpEngine(client, this, request.method(), requestHeaders, connection, null);
|
||||||
|
} else if (protocol.equals("https")) {
|
||||||
|
return new HttpsEngine(client, this, request.method(), requestHeaders, connection, null);
|
||||||
|
} else {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out the HTTP request to make in response to receiving {@code
|
||||||
|
* response}. This will either add authentication headers or follow
|
||||||
|
* redirects. If a follow-up is either unnecessary or not applicable, this
|
||||||
|
* returns null.
|
||||||
|
*/
|
||||||
|
private Request processResponse(HttpEngine engine, Response response) throws IOException {
|
||||||
|
Request request = response.request();
|
||||||
|
Proxy selectedProxy = engine.getConnection() != null
|
||||||
|
? engine.getConnection().getRoute().getProxy()
|
||||||
|
: client.getProxy();
|
||||||
|
int responseCode = response.code();
|
||||||
|
|
||||||
|
switch (responseCode) {
|
||||||
|
case HTTP_PROXY_AUTH:
|
||||||
|
if (selectedProxy.type() != Proxy.Type.HTTP) {
|
||||||
|
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
|
||||||
|
}
|
||||||
|
// fall-through
|
||||||
|
case HTTP_UNAUTHORIZED:
|
||||||
|
RawHeaders successorRequestHeaders = request.rawHeaders();
|
||||||
|
boolean credentialsFound = HttpAuthenticator.processAuthHeader(client.getAuthenticator(),
|
||||||
|
response.code(), response.rawHeaders(), successorRequestHeaders, selectedProxy,
|
||||||
|
this.request.url());
|
||||||
|
return credentialsFound
|
||||||
|
? request.newBuilder().rawHeaders(successorRequestHeaders).build()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
case HTTP_MULT_CHOICE:
|
||||||
|
case HTTP_MOVED_PERM:
|
||||||
|
case HTTP_MOVED_TEMP:
|
||||||
|
case HTTP_SEE_OTHER:
|
||||||
|
case HTTP_TEMP_REDIRECT:
|
||||||
|
String method = request.method();
|
||||||
|
if (responseCode == HTTP_TEMP_REDIRECT && !method.equals("GET") && !method.equals("HEAD")) {
|
||||||
|
// "If the 307 status code is received in response to a request other than GET or HEAD,
|
||||||
|
// the user agent MUST NOT automatically redirect the request"
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String location = response.header("Location");
|
||||||
|
if (location == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
URL url = new URL(request.url(), location);
|
||||||
|
if (!url.getProtocol().equals("https") && !url.getProtocol().equals("http")) {
|
||||||
|
return null; // Don't follow redirects to unsupported protocols.
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.request.newBuilder().url(url).build();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sameConnection(Request a, Request b) {
|
||||||
|
return a.url().getHost().equals(b.url().getHost())
|
||||||
|
&& getEffectivePort(a.url()) == getEffectivePort(b.url())
|
||||||
|
&& a.url().getProtocol().equals(b.url().getProtocol());
|
||||||
|
}
|
||||||
|
}
|
||||||
120
framework/src/com/squareup/okhttp/MediaType.java
Executable file
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a> Media Type,
|
||||||
|
* appropriate to describe the content type of an HTTP request or response body.
|
||||||
|
*/
|
||||||
|
public final class MediaType {
|
||||||
|
private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)";
|
||||||
|
private static final String QUOTED = "\"([^\"]*)\"";
|
||||||
|
private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN);
|
||||||
|
private static final Pattern PARAMETER = Pattern.compile(
|
||||||
|
";\\s*" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + ")");
|
||||||
|
|
||||||
|
private final String mediaType;
|
||||||
|
private final String type;
|
||||||
|
private final String subtype;
|
||||||
|
private final String charset;
|
||||||
|
|
||||||
|
private MediaType(String mediaType, String type, String subtype, String charset) {
|
||||||
|
this.mediaType = mediaType;
|
||||||
|
this.type = type;
|
||||||
|
this.subtype = subtype;
|
||||||
|
this.charset = charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a media type for {@code string}, or null if {@code string} is not a
|
||||||
|
* well-formed media type.
|
||||||
|
*/
|
||||||
|
public static MediaType parse(String string) {
|
||||||
|
Matcher typeSubtype = TYPE_SUBTYPE.matcher(string);
|
||||||
|
if (!typeSubtype.lookingAt()) return null;
|
||||||
|
String type = typeSubtype.group(1).toLowerCase(Locale.US);
|
||||||
|
String subtype = typeSubtype.group(2).toLowerCase(Locale.US);
|
||||||
|
|
||||||
|
String charset = null;
|
||||||
|
Matcher parameter = PARAMETER.matcher(string);
|
||||||
|
for (int s = typeSubtype.end(); s < string.length(); s = parameter.end()) {
|
||||||
|
parameter.region(s, string.length());
|
||||||
|
if (!parameter.lookingAt()) return null; // This is not a well-formed media type.
|
||||||
|
|
||||||
|
String name = parameter.group(1);
|
||||||
|
if (name == null || !name.equalsIgnoreCase("charset")) continue;
|
||||||
|
if (charset != null) throw new IllegalArgumentException("Multiple charsets: " + string);
|
||||||
|
charset = parameter.group(2) != null
|
||||||
|
? parameter.group(2) // Value is a token.
|
||||||
|
: parameter.group(3); // Value is a quoted string.
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MediaType(string, type, subtype, charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the high-level media type, such as "text", "image", "audio",
|
||||||
|
* "video", or "application".
|
||||||
|
*/
|
||||||
|
public String type() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a specific media subtype, such as "plain" or "png", "mpeg",
|
||||||
|
* "mp4" or "xml".
|
||||||
|
*/
|
||||||
|
public String subtype() {
|
||||||
|
return subtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the charset of this media type, or null if this media type doesn't
|
||||||
|
* specify a charset.
|
||||||
|
*/
|
||||||
|
public Charset charset() {
|
||||||
|
return charset != null ? Charset.forName(charset) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the charset of this media type, or {@code defaultValue} if this
|
||||||
|
* media type doesn't specify a charset.
|
||||||
|
*/
|
||||||
|
public Charset charset(Charset defaultValue) {
|
||||||
|
return charset != null ? Charset.forName(charset) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoded media type, like "text/plain; charset=utf-8",
|
||||||
|
* appropriate for use in a Content-Type header.
|
||||||
|
*/
|
||||||
|
@Override public String toString() {
|
||||||
|
return mediaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean equals(Object o) {
|
||||||
|
return o instanceof MediaType && ((MediaType) o).mediaType.equals(mediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return mediaType.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
123
framework/src/com/squareup/okhttp/OkAuthenticator.java
Executable file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.internal.Base64;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responds to authentication challenges from the remote web or proxy server by
|
||||||
|
* returning credentials.
|
||||||
|
*/
|
||||||
|
public interface OkAuthenticator {
|
||||||
|
/**
|
||||||
|
* Returns a credential that satisfies the authentication challenge made by
|
||||||
|
* {@code url}. Returns null if the challenge cannot be satisfied. This method
|
||||||
|
* is called in response to an HTTP 401 unauthorized status code sent by the
|
||||||
|
* origin server.
|
||||||
|
*
|
||||||
|
* @param challenges parsed "WWW-Authenticate" challenge headers from the HTTP
|
||||||
|
* response.
|
||||||
|
*/
|
||||||
|
Credential authenticate(Proxy proxy, URL url, List<Challenge> challenges) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a credential that satisfies the authentication challenge made by
|
||||||
|
* {@code proxy}. Returns null if the challenge cannot be satisfied. This
|
||||||
|
* method is called in response to an HTTP 401 unauthorized status code sent
|
||||||
|
* by the proxy server.
|
||||||
|
*
|
||||||
|
* @param challenges parsed "Proxy-Authenticate" challenge headers from the
|
||||||
|
* HTTP response.
|
||||||
|
*/
|
||||||
|
Credential authenticateProxy(Proxy proxy, URL url, List<Challenge> challenges) throws IOException;
|
||||||
|
|
||||||
|
/** An RFC 2617 challenge. */
|
||||||
|
public final class Challenge {
|
||||||
|
private final String scheme;
|
||||||
|
private final String realm;
|
||||||
|
|
||||||
|
public Challenge(String scheme, String realm) {
|
||||||
|
this.scheme = scheme;
|
||||||
|
this.realm = realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the authentication scheme, like {@code Basic}. */
|
||||||
|
public String getScheme() {
|
||||||
|
return scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the protection space. */
|
||||||
|
public String getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean equals(Object o) {
|
||||||
|
return o instanceof Challenge
|
||||||
|
&& ((Challenge) o).scheme.equals(scheme)
|
||||||
|
&& ((Challenge) o).realm.equals(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return scheme.hashCode() + 31 * realm.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String toString() {
|
||||||
|
return scheme + " realm=\"" + realm + "\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An RFC 2617 credential. */
|
||||||
|
public final class Credential {
|
||||||
|
private final String headerValue;
|
||||||
|
|
||||||
|
private Credential(String headerValue) {
|
||||||
|
this.headerValue = headerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns an auth credential for the Basic scheme. */
|
||||||
|
public static Credential basic(String userName, String password) {
|
||||||
|
try {
|
||||||
|
String usernameAndPassword = userName + ":" + password;
|
||||||
|
byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
|
||||||
|
String encoded = Base64.encode(bytes);
|
||||||
|
return new Credential("Basic " + encoded);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeaderValue() {
|
||||||
|
return headerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean equals(Object o) {
|
||||||
|
return o instanceof Credential && ((Credential) o).headerValue.equals(headerValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return headerValue.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String toString() {
|
||||||
|
return headerValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
228
framework/src/com/squareup/okhttp/OkHttpClient.java
Normal file → Executable file
@@ -15,34 +15,105 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.okhttp;
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.internal.Util;
|
||||||
|
import com.squareup.okhttp.internal.http.HttpAuthenticator;
|
||||||
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
|
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
|
||||||
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
|
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
|
||||||
import com.squareup.okhttp.internal.http.OkResponseCache;
|
|
||||||
import com.squareup.okhttp.internal.http.OkResponseCacheAdapter;
|
import com.squareup.okhttp.internal.http.OkResponseCacheAdapter;
|
||||||
|
import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
import java.net.ResponseCache;
|
import java.net.ResponseCache;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Collections;
|
import java.net.URLConnection;
|
||||||
import java.util.LinkedHashSet;
|
import java.net.URLStreamHandler;
|
||||||
import java.util.Set;
|
import java.net.URLStreamHandlerFactory;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
/** Configures and creates HTTP connections. */
|
/** Configures and creates HTTP connections. */
|
||||||
public final class OkHttpClient {
|
public final class OkHttpClient implements URLStreamHandlerFactory {
|
||||||
|
private static final List<String> DEFAULT_TRANSPORTS
|
||||||
|
= Util.immutableList(Arrays.asList("spdy/3", "http/1.1"));
|
||||||
|
|
||||||
|
private final RouteDatabase routeDatabase;
|
||||||
|
private final Dispatcher dispatcher;
|
||||||
private Proxy proxy;
|
private Proxy proxy;
|
||||||
private Set<Route> failedRoutes = Collections.synchronizedSet(new LinkedHashSet<Route>());
|
private List<String> transports;
|
||||||
private ProxySelector proxySelector;
|
private ProxySelector proxySelector;
|
||||||
private CookieHandler cookieHandler;
|
private CookieHandler cookieHandler;
|
||||||
private ResponseCache responseCache;
|
private ResponseCache responseCache;
|
||||||
private SSLSocketFactory sslSocketFactory;
|
private SSLSocketFactory sslSocketFactory;
|
||||||
private HostnameVerifier hostnameVerifier;
|
private HostnameVerifier hostnameVerifier;
|
||||||
|
private OkAuthenticator authenticator;
|
||||||
private ConnectionPool connectionPool;
|
private ConnectionPool connectionPool;
|
||||||
private boolean followProtocolRedirects = true;
|
private boolean followProtocolRedirects = true;
|
||||||
|
private int connectTimeout;
|
||||||
|
private int readTimeout;
|
||||||
|
|
||||||
|
public OkHttpClient() {
|
||||||
|
routeDatabase = new RouteDatabase();
|
||||||
|
dispatcher = new Dispatcher();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OkHttpClient(OkHttpClient copyFrom) {
|
||||||
|
routeDatabase = copyFrom.routeDatabase;
|
||||||
|
dispatcher = copyFrom.dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default connect timeout for new connections. A value of 0 means no timeout.
|
||||||
|
*
|
||||||
|
* @see URLConnection#setConnectTimeout(int)
|
||||||
|
*/
|
||||||
|
public void setConnectTimeout(long timeout, TimeUnit unit) {
|
||||||
|
if (timeout < 0) {
|
||||||
|
throw new IllegalArgumentException("timeout < 0");
|
||||||
|
}
|
||||||
|
if (unit == null) {
|
||||||
|
throw new IllegalArgumentException("unit == null");
|
||||||
|
}
|
||||||
|
long millis = unit.toMillis(timeout);
|
||||||
|
if (millis > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("Timeout too large.");
|
||||||
|
}
|
||||||
|
connectTimeout = (int) millis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default connect timeout (in milliseconds). */
|
||||||
|
public int getConnectTimeout() {
|
||||||
|
return connectTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default read timeout for new connections. A value of 0 means no timeout.
|
||||||
|
*
|
||||||
|
* @see URLConnection#setReadTimeout(int)
|
||||||
|
*/
|
||||||
|
public void setReadTimeout(long timeout, TimeUnit unit) {
|
||||||
|
if (timeout < 0) {
|
||||||
|
throw new IllegalArgumentException("timeout < 0");
|
||||||
|
}
|
||||||
|
if (unit == null) {
|
||||||
|
throw new IllegalArgumentException("unit == null");
|
||||||
|
}
|
||||||
|
long millis = unit.toMillis(timeout);
|
||||||
|
if (millis > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("Timeout too large.");
|
||||||
|
}
|
||||||
|
readTimeout = (int) millis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default read timeout (in milliseconds). */
|
||||||
|
public int getReadTimeout() {
|
||||||
|
return readTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the HTTP proxy that will be used by connections created by this
|
* Sets the HTTP proxy that will be used by connections created by this
|
||||||
@@ -108,7 +179,7 @@ public final class OkHttpClient {
|
|||||||
return responseCache;
|
return responseCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
private OkResponseCache okResponseCache() {
|
public OkResponseCache getOkResponseCache() {
|
||||||
if (responseCache instanceof HttpResponseCache) {
|
if (responseCache instanceof HttpResponseCache) {
|
||||||
return ((HttpResponseCache) responseCache).okResponseCache;
|
return ((HttpResponseCache) responseCache).okResponseCache;
|
||||||
} else if (responseCache != null) {
|
} else if (responseCache != null) {
|
||||||
@@ -124,7 +195,7 @@ public final class OkHttpClient {
|
|||||||
* <p>If unset, the {@link HttpsURLConnection#getDefaultSSLSocketFactory()
|
* <p>If unset, the {@link HttpsURLConnection#getDefaultSSLSocketFactory()
|
||||||
* system-wide default} SSL socket factory will be used.
|
* system-wide default} SSL socket factory will be used.
|
||||||
*/
|
*/
|
||||||
public OkHttpClient setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
|
public OkHttpClient setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
|
||||||
this.sslSocketFactory = sslSocketFactory;
|
this.sslSocketFactory = sslSocketFactory;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -149,6 +220,22 @@ public final class OkHttpClient {
|
|||||||
return hostnameVerifier;
|
return hostnameVerifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authenticator used to respond to challenges from the remote web
|
||||||
|
* server or proxy server.
|
||||||
|
*
|
||||||
|
* <p>If unset, the {@link java.net.Authenticator#setDefault system-wide default}
|
||||||
|
* authenticator will be used.
|
||||||
|
*/
|
||||||
|
public OkHttpClient setAuthenticator(OkAuthenticator authenticator) {
|
||||||
|
this.authenticator = authenticator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OkAuthenticator getAuthenticator() {
|
||||||
|
return authenticator;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the connection pool used to recycle HTTP and HTTPS connections.
|
* Sets the connection pool used to recycle HTTP and HTTPS connections.
|
||||||
*
|
*
|
||||||
@@ -180,16 +267,86 @@ public final class OkHttpClient {
|
|||||||
return followProtocolRedirects;
|
return followProtocolRedirects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RouteDatabase getRoutesDatabase() {
|
||||||
|
return routeDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the transports used by this client to communicate with remote
|
||||||
|
* servers. By default this client will prefer the most efficient transport
|
||||||
|
* available, falling back to more ubiquitous transports. Applications should
|
||||||
|
* only call this method to avoid specific compatibility problems, such as web
|
||||||
|
* servers that behave incorrectly when SPDY is enabled.
|
||||||
|
*
|
||||||
|
* <p>The following transports are currently supported:
|
||||||
|
* <ul>
|
||||||
|
* <li><a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">http/1.1</a>
|
||||||
|
* <li><a href="http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3">spdy/3</a>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p><strong>This is an evolving set.</strong> Future releases may drop
|
||||||
|
* support for transitional transports (like spdy/3), in favor of their
|
||||||
|
* successors (spdy/4 or http/2.0). The http/1.1 transport will never be
|
||||||
|
* dropped.
|
||||||
|
*
|
||||||
|
* <p>If multiple protocols are specified, <a
|
||||||
|
* href="https://technotes.googlecode.com/git/nextprotoneg.html">NPN</a> will
|
||||||
|
* be used to negotiate a transport. Future releases may use another mechanism
|
||||||
|
* (such as <a href="http://tools.ietf.org/html/draft-friedl-tls-applayerprotoneg-02">ALPN</a>)
|
||||||
|
* to negotiate a transport.
|
||||||
|
*
|
||||||
|
* @param transports the transports to use, in order of preference. The list
|
||||||
|
* must contain "http/1.1". It must not contain null.
|
||||||
|
*/
|
||||||
|
public OkHttpClient setTransports(List<String> transports) {
|
||||||
|
transports = Util.immutableList(transports);
|
||||||
|
if (!transports.contains("http/1.1")) {
|
||||||
|
throw new IllegalArgumentException("transports doesn't contain http/1.1: " + transports);
|
||||||
|
}
|
||||||
|
if (transports.contains(null)) {
|
||||||
|
throw new IllegalArgumentException("transports must not contain null");
|
||||||
|
}
|
||||||
|
if (transports.contains("")) {
|
||||||
|
throw new IllegalArgumentException("transports contains an empty string");
|
||||||
|
}
|
||||||
|
this.transports = transports;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getTransports() {
|
||||||
|
return transports;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules {@code request} to be executed.
|
||||||
|
*/
|
||||||
|
/* OkHttp 2.0: public */ void enqueue(Request request, Response.Receiver responseReceiver) {
|
||||||
|
// Create the HttpURLConnection immediately so the enqueued job gets the current settings of
|
||||||
|
// this client. Otherwise changes to this client (socket factory, redirect policy, etc.) may
|
||||||
|
// incorrectly be reflected in the request when it is dispatched later.
|
||||||
|
dispatcher.enqueue(copyWithDefaults(), request, responseReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels all scheduled tasks tagged with {@code tag}. Requests that are already
|
||||||
|
* in flight might not be canceled.
|
||||||
|
*/
|
||||||
|
/* OkHttp 2.0: public */ void cancel(Object tag) {
|
||||||
|
dispatcher.cancel(tag);
|
||||||
|
}
|
||||||
|
|
||||||
public HttpURLConnection open(URL url) {
|
public HttpURLConnection open(URL url) {
|
||||||
|
return open(url, proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpURLConnection open(URL url, Proxy proxy) {
|
||||||
String protocol = url.getProtocol();
|
String protocol = url.getProtocol();
|
||||||
OkHttpClient copy = copyWithDefaults();
|
OkHttpClient copy = copyWithDefaults();
|
||||||
if (protocol.equals("http")) {
|
copy.proxy = proxy;
|
||||||
return new HttpURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
|
|
||||||
} else if (protocol.equals("https")) {
|
if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);
|
||||||
return new HttpsURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
|
if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);
|
||||||
} else {
|
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
|
||||||
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,9 +354,8 @@ public final class OkHttpClient {
|
|||||||
* each field that hasn't been explicitly configured.
|
* each field that hasn't been explicitly configured.
|
||||||
*/
|
*/
|
||||||
private OkHttpClient copyWithDefaults() {
|
private OkHttpClient copyWithDefaults() {
|
||||||
OkHttpClient result = new OkHttpClient();
|
OkHttpClient result = new OkHttpClient(this);
|
||||||
result.proxy = proxy;
|
result.proxy = proxy;
|
||||||
result.failedRoutes = failedRoutes;
|
|
||||||
result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
|
result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
|
||||||
result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
|
result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
|
||||||
result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();
|
result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();
|
||||||
@@ -208,9 +364,45 @@ public final class OkHttpClient {
|
|||||||
: HttpsURLConnection.getDefaultSSLSocketFactory();
|
: HttpsURLConnection.getDefaultSSLSocketFactory();
|
||||||
result.hostnameVerifier = hostnameVerifier != null
|
result.hostnameVerifier = hostnameVerifier != null
|
||||||
? hostnameVerifier
|
? hostnameVerifier
|
||||||
: HttpsURLConnection.getDefaultHostnameVerifier();
|
: OkHostnameVerifier.INSTANCE;
|
||||||
|
result.authenticator = authenticator != null
|
||||||
|
? authenticator
|
||||||
|
: HttpAuthenticator.SYSTEM_DEFAULT;
|
||||||
result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
|
result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
|
||||||
result.followProtocolRedirects = followProtocolRedirects;
|
result.followProtocolRedirects = followProtocolRedirects;
|
||||||
|
result.transports = transports != null ? transports : DEFAULT_TRANSPORTS;
|
||||||
|
result.connectTimeout = connectTimeout;
|
||||||
|
result.readTimeout = readTimeout;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a URLStreamHandler as a {@link URL#setURLStreamHandlerFactory}.
|
||||||
|
*
|
||||||
|
* <p>This code configures OkHttp to handle all HTTP and HTTPS connections
|
||||||
|
* created with {@link URL#openConnection()}: <pre> {@code
|
||||||
|
*
|
||||||
|
* OkHttpClient okHttpClient = new OkHttpClient();
|
||||||
|
* URL.setURLStreamHandlerFactory(okHttpClient);
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public URLStreamHandler createURLStreamHandler(final String protocol) {
|
||||||
|
if (!protocol.equals("http") && !protocol.equals("https")) return null;
|
||||||
|
|
||||||
|
return new URLStreamHandler() {
|
||||||
|
@Override protected URLConnection openConnection(URL url) {
|
||||||
|
return open(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected URLConnection openConnection(URL url, Proxy proxy) {
|
||||||
|
return open(url, proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected int getDefaultPort() {
|
||||||
|
if (protocol.equals("http")) return 80;
|
||||||
|
if (protocol.equals("https")) return 443;
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
framework/src/com/squareup/okhttp/OkResponseCache.java
Normal file → Executable file
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012 The Android Open Source Project
|
* Copyright (C) 2013 Square, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,23 +16,41 @@
|
|||||||
package com.squareup.okhttp;
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.CacheRequest;
|
||||||
import java.net.CacheResponse;
|
import java.net.CacheResponse;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A response cache that supports statistics tracking and updating stored
|
* An extended response cache API. Unlike {@link java.net.ResponseCache}, this
|
||||||
* responses. Implementations of {@link java.net.ResponseCache} should implement
|
* interface supports conditional caching and statistics.
|
||||||
* this interface to receive additional support from the HTTP engine.
|
*
|
||||||
|
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||||
|
* This class is in beta. APIs are subject to change!
|
||||||
*/
|
*/
|
||||||
public interface OkResponseCache {
|
public interface OkResponseCache {
|
||||||
|
CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders)
|
||||||
|
throws IOException;
|
||||||
|
|
||||||
/** Track an HTTP response being satisfied by {@code source}. */
|
CacheRequest put(URI uri, URLConnection urlConnection) throws IOException;
|
||||||
void trackResponse(ResponseSource source);
|
|
||||||
|
/** Remove any cache entries for the supplied {@code uri} if the request method invalidates. */
|
||||||
|
void maybeRemove(String requestMethod, URI uri) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a conditional request hit by updating the stored cache response
|
||||||
|
* with the headers from {@code httpConnection}. The cached response body is
|
||||||
|
* not updated. If the stored response has changed since {@code
|
||||||
|
* conditionalCacheHit} was returned, this does nothing.
|
||||||
|
*/
|
||||||
|
void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException;
|
||||||
|
|
||||||
/** Track an conditional GET that was satisfied by this cache. */
|
/** Track an conditional GET that was satisfied by this cache. */
|
||||||
void trackConditionalCacheHit();
|
void trackConditionalCacheHit();
|
||||||
|
|
||||||
/** Updates stored HTTP headers using a hit on a conditional GET. */
|
/** Track an HTTP response being satisfied by {@code source}. */
|
||||||
void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
|
void trackResponse(ResponseSource source);
|
||||||
throws IOException;
|
|
||||||
}
|
}
|
||||||
|
|||||||
284
framework/src/com/squareup/okhttp/Request.java
Executable file
@@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.internal.Util;
|
||||||
|
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An HTTP request. Instances of this class are immutable if their {@link #body}
|
||||||
|
* is null or itself immutable.
|
||||||
|
*
|
||||||
|
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||||
|
* This class is in beta. APIs are subject to change!
|
||||||
|
*/
|
||||||
|
/* OkHttp 2.0: public */ final class Request {
|
||||||
|
private final URL url;
|
||||||
|
private final String method;
|
||||||
|
private final RawHeaders headers;
|
||||||
|
private final Body body;
|
||||||
|
private final Object tag;
|
||||||
|
|
||||||
|
private Request(Builder builder) {
|
||||||
|
this.url = builder.url;
|
||||||
|
this.method = builder.method;
|
||||||
|
this.headers = new RawHeaders(builder.headers);
|
||||||
|
this.body = builder.body;
|
||||||
|
this.tag = builder.tag != null ? builder.tag : this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL url() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String urlString() {
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String method() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String header(String name) {
|
||||||
|
return headers.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> headers(String name) {
|
||||||
|
return headers.values(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> headerNames() {
|
||||||
|
return headers.names();
|
||||||
|
}
|
||||||
|
|
||||||
|
RawHeaders rawHeaders() {
|
||||||
|
return new RawHeaders(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int headerCount() {
|
||||||
|
return headers.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String headerName(int index) {
|
||||||
|
return headers.getFieldName(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String headerValue(int index) {
|
||||||
|
return headers.getValue(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Body body() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object tag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder newBuilder() {
|
||||||
|
return new Builder(url)
|
||||||
|
.method(method, body)
|
||||||
|
.rawHeaders(headers)
|
||||||
|
.tag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class Body {
|
||||||
|
/** Returns the Content-Type header for this body. */
|
||||||
|
public abstract MediaType contentType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes that will be written to {@code out} in a call
|
||||||
|
* to {@link #writeTo}, or -1 if that count is unknown.
|
||||||
|
*/
|
||||||
|
public long contentLength() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Writes the content of this request to {@code out}. */
|
||||||
|
public abstract void writeTo(OutputStream out) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new request body that transmits {@code content}. If {@code
|
||||||
|
* contentType} lacks a charset, this will use UTF-8.
|
||||||
|
*/
|
||||||
|
public static Body create(MediaType contentType, String content) {
|
||||||
|
contentType = contentType.charset() != null
|
||||||
|
? contentType
|
||||||
|
: MediaType.parse(contentType + "; charset=utf-8");
|
||||||
|
try {
|
||||||
|
byte[] bytes = content.getBytes(contentType.charset().name());
|
||||||
|
return create(contentType, bytes);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new request body that transmits {@code content}. */
|
||||||
|
public static Body create(final MediaType contentType, final byte[] content) {
|
||||||
|
if (contentType == null) throw new NullPointerException("contentType == null");
|
||||||
|
if (content == null) throw new NullPointerException("content == null");
|
||||||
|
|
||||||
|
return new Body() {
|
||||||
|
@Override public MediaType contentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public long contentLength() {
|
||||||
|
return content.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeTo(OutputStream out) throws IOException {
|
||||||
|
out.write(content);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new request body that transmits the content of {@code file}. */
|
||||||
|
public static Body create(final MediaType contentType, final File file) {
|
||||||
|
if (contentType == null) throw new NullPointerException("contentType == null");
|
||||||
|
if (file == null) throw new NullPointerException("content == null");
|
||||||
|
|
||||||
|
return new Body() {
|
||||||
|
@Override public MediaType contentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public long contentLength() {
|
||||||
|
return file.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeTo(OutputStream out) throws IOException {
|
||||||
|
long length = contentLength();
|
||||||
|
if (length == 0) return;
|
||||||
|
|
||||||
|
InputStream in = null;
|
||||||
|
try {
|
||||||
|
in = new FileInputStream(file);
|
||||||
|
byte[] buffer = new byte[(int) Math.min(8192, length)];
|
||||||
|
for (int c; (c = in.read(buffer)) != -1; ) {
|
||||||
|
out.write(buffer, 0, c);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Util.closeQuietly(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private URL url;
|
||||||
|
private String method = "GET";
|
||||||
|
private RawHeaders headers = new RawHeaders();
|
||||||
|
private Body body;
|
||||||
|
private Object tag;
|
||||||
|
|
||||||
|
public Builder(String url) {
|
||||||
|
url(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(URL url) {
|
||||||
|
url(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder url(String url) {
|
||||||
|
try {
|
||||||
|
this.url = new URL(url);
|
||||||
|
return this;
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new IllegalArgumentException("Malformed URL: " + url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder url(URL url) {
|
||||||
|
if (url == null) throw new IllegalStateException("url == null");
|
||||||
|
this.url = url;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the header named {@code name} to {@code value}. If this request
|
||||||
|
* already has any headers with that name, they are all replaced.
|
||||||
|
*/
|
||||||
|
public Builder header(String name, String value) {
|
||||||
|
headers.set(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a header with {@code name} and {@code value}. Prefer this method for
|
||||||
|
* multiply-valued headers like "Cookie".
|
||||||
|
*/
|
||||||
|
public Builder addHeader(String name, String value) {
|
||||||
|
headers.add(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder rawHeaders(RawHeaders rawHeaders) {
|
||||||
|
headers = new RawHeaders(rawHeaders);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder get() {
|
||||||
|
return method("GET", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder head() {
|
||||||
|
return method("HEAD", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder post(Body body) {
|
||||||
|
return method("POST", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder put(Body body) {
|
||||||
|
return method("PUT", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder method(String method, Body body) {
|
||||||
|
if (method == null || method.length() == 0) {
|
||||||
|
throw new IllegalArgumentException("method == null || method.length() == 0");
|
||||||
|
}
|
||||||
|
this.method = method;
|
||||||
|
this.body = body;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches {@code tag} to the request. It can be used later to cancel the
|
||||||
|
* request. If the tag is unspecified or null, the request is canceled by
|
||||||
|
* using the request itself as the tag.
|
||||||
|
*/
|
||||||
|
public Builder tag(Object tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request build() {
|
||||||
|
return new Request(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
290
framework/src/com/squareup/okhttp/Response.java
Executable file
@@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.internal.Util;
|
||||||
|
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.internal.Util.UTF_8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An HTTP response. Instances of this class are not immutable: the response
|
||||||
|
* body is a one-shot value that may be consumed only once. All other properties
|
||||||
|
* are immutable.
|
||||||
|
*
|
||||||
|
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||||
|
* This class is in beta. APIs are subject to change!
|
||||||
|
*/
|
||||||
|
/* OkHttp 2.0: public */ final class Response {
|
||||||
|
private final Request request;
|
||||||
|
private final int code;
|
||||||
|
private final RawHeaders headers;
|
||||||
|
private final Body body;
|
||||||
|
private final Response redirectedBy;
|
||||||
|
|
||||||
|
private Response(Builder builder) {
|
||||||
|
this.request = builder.request;
|
||||||
|
this.code = builder.code;
|
||||||
|
this.headers = new RawHeaders(builder.headers);
|
||||||
|
this.body = builder.body;
|
||||||
|
this.redirectedBy = builder.redirectedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wire-level request that initiated this HTTP response. This is usually
|
||||||
|
* <strong>not</strong> the same request instance provided to the HTTP client:
|
||||||
|
* <ul>
|
||||||
|
* <li>It may be transformed by the HTTP client. For example, the client
|
||||||
|
* may have added its own {@code Content-Encoding} header to enable
|
||||||
|
* response compression.
|
||||||
|
* <li>It may be the request generated in response to an HTTP redirect.
|
||||||
|
* In this case the request URL may be different than the initial
|
||||||
|
* request URL.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public Request request() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int code() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String header(String name) {
|
||||||
|
return header(name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String header(String name, String defaultValue) {
|
||||||
|
String result = headers.get(name);
|
||||||
|
return result != null ? result : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> headers(String name) {
|
||||||
|
return headers.values(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> headerNames() {
|
||||||
|
return headers.names();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int headerCount() {
|
||||||
|
return headers.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String headerName(int index) {
|
||||||
|
return headers.getFieldName(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
RawHeaders rawHeaders() {
|
||||||
|
return new RawHeaders(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String headerValue(int index) {
|
||||||
|
return headers.getValue(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Body body() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the response for the HTTP redirect that triggered this response, or
|
||||||
|
* null if this response wasn't triggered by an automatic redirect. The body
|
||||||
|
* of the returned response should not be read because it has already been
|
||||||
|
* consumed by the redirecting client.
|
||||||
|
*/
|
||||||
|
public Response redirectedBy() {
|
||||||
|
return redirectedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class Body {
|
||||||
|
/** Multiple calls to {@link #charStream()} must return the same instance. */
|
||||||
|
private Reader reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if further data from this response body should be read at
|
||||||
|
* this time. For asynchronous transports like SPDY and HTTP/2.0, this will
|
||||||
|
* return false once all locally-available body bytes have been read.
|
||||||
|
*
|
||||||
|
* <p>Clients with many concurrent downloads can use this method to reduce
|
||||||
|
* the number of idle threads blocking on reads. See {@link
|
||||||
|
* Receiver#onResponse} for details.
|
||||||
|
*/
|
||||||
|
// <h3>Body.ready() vs. InputStream.available()</h3>
|
||||||
|
// TODO: Can we fix response bodies to implement InputStream.available well?
|
||||||
|
// The deflater implementation is broken by default but we could do better.
|
||||||
|
public abstract boolean ready() throws IOException;
|
||||||
|
|
||||||
|
public abstract MediaType contentType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes in that will returned by {@link #bytes}, or
|
||||||
|
* {@link #byteStream}, or -1 if unknown.
|
||||||
|
*/
|
||||||
|
public abstract long contentLength();
|
||||||
|
|
||||||
|
public abstract InputStream byteStream() throws IOException;
|
||||||
|
|
||||||
|
public final byte[] bytes() throws IOException {
|
||||||
|
long contentLength = contentLength();
|
||||||
|
if (contentLength > Integer.MAX_VALUE) {
|
||||||
|
throw new IOException("Cannot buffer entire body for content length: " + contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentLength != -1) {
|
||||||
|
byte[] content = new byte[(int) contentLength];
|
||||||
|
InputStream in = byteStream();
|
||||||
|
Util.readFully(in, content);
|
||||||
|
if (in.read() != -1) throw new IOException("Content-Length and stream length disagree");
|
||||||
|
return content;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
Util.copy(byteStream(), out);
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the response as a character stream decoded with the charset
|
||||||
|
* of the Content-Type header. If that header is either absent or lacks a
|
||||||
|
* charset, this will attempt to decode the response body as UTF-8.
|
||||||
|
*/
|
||||||
|
public final Reader charStream() throws IOException {
|
||||||
|
if (reader == null) {
|
||||||
|
reader = new InputStreamReader(byteStream(), charset());
|
||||||
|
}
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the response as a string decoded with the charset of the
|
||||||
|
* Content-Type header. If that header is either absent or lacks a charset,
|
||||||
|
* this will attempt to decode the response body as UTF-8.
|
||||||
|
*/
|
||||||
|
public final String string() throws IOException {
|
||||||
|
return new String(bytes(), charset().name());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Charset charset() {
|
||||||
|
MediaType contentType = contentType();
|
||||||
|
return contentType != null ? contentType.charset(UTF_8) : UTF_8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Receiver {
|
||||||
|
/**
|
||||||
|
* Called when the request could not be executed due to a connectivity
|
||||||
|
* problem or timeout. Because networks can fail during an exchange, it is
|
||||||
|
* possible that the remote server accepted the request before the failure.
|
||||||
|
*/
|
||||||
|
void onFailure(Failure failure);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the HTTP response was successfully returned by the remote
|
||||||
|
* server. The receiver may proceed to read the response body with the
|
||||||
|
* response's {@link #body} method.
|
||||||
|
*
|
||||||
|
* <p>Note that transport-layer success (receiving a HTTP response code,
|
||||||
|
* headers and body) does not necessarily indicate application-layer
|
||||||
|
* success: {@code response} may still indicate an unhappy HTTP response
|
||||||
|
* code like 404 or 500.
|
||||||
|
*
|
||||||
|
* <h3>Non-blocking responses</h3>
|
||||||
|
*
|
||||||
|
* <p>Receivers do not need to block while waiting for the response body to
|
||||||
|
* download. Instead, they can get called back as data arrives. Use {@link
|
||||||
|
* Body#ready} to check if bytes should be read immediately. While there is
|
||||||
|
* data ready, read it. If there isn't, return false: receivers will be
|
||||||
|
* called back with {@code onResponse()} as additional data is downloaded.
|
||||||
|
*
|
||||||
|
* <p>Return true to indicate that the receiver has finished handling the
|
||||||
|
* response body. If the response body has unread data, it will be
|
||||||
|
* discarded.
|
||||||
|
*
|
||||||
|
* <p>When the response body has been fully consumed the returned value is
|
||||||
|
* undefined.
|
||||||
|
*
|
||||||
|
* <p>The current implementation of {@link Body#ready} always returns true
|
||||||
|
* when the underlying transport is HTTP/1. This results in blocking on that
|
||||||
|
* transport. For effective non-blocking your server must support SPDY or
|
||||||
|
* HTTP/2.
|
||||||
|
*/
|
||||||
|
boolean onResponse(Response response) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private final Request request;
|
||||||
|
private final int code;
|
||||||
|
private RawHeaders headers = new RawHeaders();
|
||||||
|
private Body body;
|
||||||
|
private Response redirectedBy;
|
||||||
|
|
||||||
|
public Builder(Request request, int code) {
|
||||||
|
if (request == null) throw new IllegalArgumentException("request == null");
|
||||||
|
if (code <= 0) throw new IllegalArgumentException("code <= 0");
|
||||||
|
this.request = request;
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the header named {@code name} to {@code value}. If this request
|
||||||
|
* already has any headers with that name, they are all replaced.
|
||||||
|
*/
|
||||||
|
public Builder header(String name, String value) {
|
||||||
|
headers.set(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a header with {@code name} and {@code value}. Prefer this method for
|
||||||
|
* multiply-valued headers like "Set-Cookie".
|
||||||
|
*/
|
||||||
|
public Builder addHeader(String name, String value) {
|
||||||
|
headers.add(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder rawHeaders(RawHeaders rawHeaders) {
|
||||||
|
headers = new RawHeaders(rawHeaders);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder body(Body body) {
|
||||||
|
this.body = body;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder redirectedBy(Response redirectedBy) {
|
||||||
|
this.redirectedBy = redirectedBy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response build() {
|
||||||
|
if (request == null) throw new IllegalStateException("Response has no request.");
|
||||||
|
if (code == -1) throw new IllegalStateException("Response has no code.");
|
||||||
|
return new Response(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
framework/src/com/squareup/okhttp/ResponseSource.java
Normal file → Executable file
6
framework/src/com/squareup/okhttp/Route.java
Normal file → Executable file
@@ -59,13 +59,13 @@ public class Route {
|
|||||||
return inetSocketAddress;
|
return inetSocketAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns true if this route uses modern tls. */
|
/** Returns true if this route uses modern TLS. */
|
||||||
public boolean isModernTls() {
|
public boolean isModernTls() {
|
||||||
return modernTls;
|
return modernTls;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a copy of this route with flipped tls mode. */
|
/** Returns a copy of this route with flipped TLS mode. */
|
||||||
public Route flipTlsMode() {
|
Route flipTlsMode() {
|
||||||
return new Route(address, proxy, inetSocketAddress, !modernTls);
|
return new Route(address, proxy, inetSocketAddress, !modernTls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
framework/src/com/squareup/okhttp/RouteDatabase.java
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A blacklist of failed routes to avoid when creating a new connection to a
|
||||||
|
* target address. This is used so that OkHttp can learn from its mistakes: if
|
||||||
|
* there was a failure attempting to connect to a specific IP address, proxy
|
||||||
|
* server or TLS mode, that failure is remembered and alternate routes are
|
||||||
|
* preferred.
|
||||||
|
*/
|
||||||
|
public final class RouteDatabase {
|
||||||
|
private final Set<Route> failedRoutes = new LinkedHashSet<Route>();
|
||||||
|
|
||||||
|
/** Records a failure connecting to {@code failedRoute}. */
|
||||||
|
public synchronized void failed(Route failedRoute, IOException failure) {
|
||||||
|
failedRoutes.add(failedRoute);
|
||||||
|
|
||||||
|
if (!(failure instanceof SSLHandshakeException)) {
|
||||||
|
// If the problem was not related to SSL then it will also fail with
|
||||||
|
// a different TLS mode therefore we can be proactive about it.
|
||||||
|
failedRoutes.add(failedRoute.flipTlsMode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Records success connecting to {@code failedRoute}. */
|
||||||
|
public synchronized void connected(Route route) {
|
||||||
|
failedRoutes.remove(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if {@code route} has failed recently and should be avoided. */
|
||||||
|
public synchronized boolean shouldPostpone(Route route) {
|
||||||
|
return failedRoutes.contains(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int failedRoutesCount() {
|
||||||
|
return failedRoutes.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
0
framework/src/com/squareup/okhttp/TunnelRequest.java
Normal file → Executable file
0
framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java
Normal file → Executable file
0
framework/src/com/squareup/okhttp/internal/Base64.java
Normal file → Executable file
0
framework/src/com/squareup/okhttp/internal/DiskLruCache.java
Normal file → Executable file
0
framework/src/com/squareup/okhttp/internal/Dns.java
Normal file → Executable file
0
framework/src/com/squareup/okhttp/internal/FaultRecoveringOutputStream.java
Normal file → Executable file
6
framework/src/com/squareup/okhttp/internal/NamedRunnable.java
Normal file → Executable file
@@ -20,10 +20,10 @@ package com.squareup.okhttp.internal;
|
|||||||
* Runnable implementation which always sets its thread name.
|
* Runnable implementation which always sets its thread name.
|
||||||
*/
|
*/
|
||||||
public abstract class NamedRunnable implements Runnable {
|
public abstract class NamedRunnable implements Runnable {
|
||||||
private String name;
|
private final String name;
|
||||||
|
|
||||||
public NamedRunnable(String name) {
|
public NamedRunnable(String format, Object... args) {
|
||||||
this.name = name;
|
this.name = String.format(format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public final void run() {
|
@Override public final void run() {
|
||||||
|
|||||||
117
framework/src/com/squareup/okhttp/internal/Platform.java
Normal file → Executable file
@@ -16,7 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.okhttp.internal;
|
package com.squareup.okhttp.internal;
|
||||||
|
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
@@ -25,7 +24,7 @@ import java.lang.reflect.InvocationHandler;
|
|||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.net.NetworkInterface;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -57,6 +56,11 @@ public class Platform {
|
|||||||
return PLATFORM;
|
return PLATFORM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Prefix used on custom headers. */
|
||||||
|
public String getPrefix() {
|
||||||
|
return "OkHttp";
|
||||||
|
}
|
||||||
|
|
||||||
public void logW(String warning) {
|
public void logW(String warning) {
|
||||||
System.out.println(warning);
|
System.out.println(warning);
|
||||||
}
|
}
|
||||||
@@ -99,6 +103,11 @@ public class Platform {
|
|||||||
public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
|
public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void connectSocket(Socket socket, InetSocketAddress address,
|
||||||
|
int connectTimeout) throws IOException {
|
||||||
|
socket.connect(address, connectTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a deflater output stream that supports SYNC_FLUSH for SPDY name
|
* Returns a deflater output stream that supports SYNC_FLUSH for SPDY name
|
||||||
* value blocks. This throws an {@link UnsupportedOperationException} on
|
* value blocks. This throws an {@link UnsupportedOperationException} on
|
||||||
@@ -125,33 +134,21 @@ public class Platform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the maximum transmission unit of the network interface used by
|
|
||||||
* {@code socket}, or a reasonable default if this platform doesn't expose the
|
|
||||||
* MTU to the application layer.
|
|
||||||
*
|
|
||||||
* <p>The returned value should only be used as an optimization; such as to
|
|
||||||
* size buffers efficiently.
|
|
||||||
*/
|
|
||||||
public int getMtu(Socket socket) throws IOException {
|
|
||||||
return 1400; // Smaller than 1500 to leave room for headers on interfaces like PPPoE.
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Attempt to match the host runtime to a capable Platform implementation. */
|
/** Attempt to match the host runtime to a capable Platform implementation. */
|
||||||
private static Platform findPlatform() {
|
private static Platform findPlatform() {
|
||||||
Method getMtu;
|
|
||||||
try {
|
|
||||||
getMtu = NetworkInterface.class.getMethod("getMTU");
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
return new Platform(); // No Java 1.6 APIs. It's either Java 1.5, Android 2.2 or earlier.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to find Android 2.3+ APIs.
|
// Attempt to find Android 2.3+ APIs.
|
||||||
Class<?> openSslSocketClass;
|
Class<?> openSslSocketClass;
|
||||||
Method setUseSessionTickets;
|
Method setUseSessionTickets;
|
||||||
Method setHostname;
|
Method setHostname;
|
||||||
try {
|
try {
|
||||||
openSslSocketClass = Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
|
try {
|
||||||
|
openSslSocketClass = Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl");
|
||||||
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
// Older platform before being unbundled.
|
||||||
|
openSslSocketClass = Class.forName(
|
||||||
|
"org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
|
||||||
|
}
|
||||||
|
|
||||||
setUseSessionTickets = openSslSocketClass.getMethod("setUseSessionTickets", boolean.class);
|
setUseSessionTickets = openSslSocketClass.getMethod("setUseSessionTickets", boolean.class);
|
||||||
setHostname = openSslSocketClass.getMethod("setHostname", String.class);
|
setHostname = openSslSocketClass.getMethod("setHostname", String.class);
|
||||||
|
|
||||||
@@ -159,10 +156,10 @@ public class Platform {
|
|||||||
try {
|
try {
|
||||||
Method setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class);
|
Method setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class);
|
||||||
Method getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol");
|
Method getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol");
|
||||||
return new Android41(getMtu, openSslSocketClass, setUseSessionTickets, setHostname,
|
return new Android41(openSslSocketClass, setUseSessionTickets, setHostname,
|
||||||
setNpnProtocols, getNpnSelectedProtocol);
|
setNpnProtocols, getNpnSelectedProtocol);
|
||||||
} catch (NoSuchMethodException ignored) {
|
} catch (NoSuchMethodException ignored) {
|
||||||
return new Android23(getMtu, openSslSocketClass, setUseSessionTickets, setHostname);
|
return new Android23(openSslSocketClass, setUseSessionTickets, setHostname);
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException ignored) {
|
} catch (ClassNotFoundException ignored) {
|
||||||
// This isn't an Android runtime.
|
// This isn't an Android runtime.
|
||||||
@@ -179,55 +176,43 @@ public class Platform {
|
|||||||
Class<?> serverProviderClass = Class.forName(npnClassName + "$ServerProvider");
|
Class<?> serverProviderClass = Class.forName(npnClassName + "$ServerProvider");
|
||||||
Method putMethod = nextProtoNegoClass.getMethod("put", SSLSocket.class, providerClass);
|
Method putMethod = nextProtoNegoClass.getMethod("put", SSLSocket.class, providerClass);
|
||||||
Method getMethod = nextProtoNegoClass.getMethod("get", SSLSocket.class);
|
Method getMethod = nextProtoNegoClass.getMethod("get", SSLSocket.class);
|
||||||
return new JdkWithJettyNpnPlatform(getMtu, putMethod, getMethod, clientProviderClass,
|
return new JdkWithJettyNpnPlatform(
|
||||||
serverProviderClass);
|
putMethod, getMethod, clientProviderClass, serverProviderClass);
|
||||||
} catch (ClassNotFoundException ignored) {
|
} catch (ClassNotFoundException ignored) {
|
||||||
// NPN isn't on the classpath.
|
// NPN isn't on the classpath.
|
||||||
} catch (NoSuchMethodException ignored) {
|
} catch (NoSuchMethodException ignored) {
|
||||||
// The NPN version isn't what we expect.
|
// The NPN version isn't what we expect.
|
||||||
}
|
}
|
||||||
|
|
||||||
return getMtu != null ? new Java5(getMtu) : new Platform();
|
return new Platform();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Java5 extends Platform {
|
/** Android version 2.3 and newer support TLS session tickets and server name indication (SNI). */
|
||||||
private final Method getMtu;
|
private static class Android23 extends Platform {
|
||||||
|
|
||||||
private Java5(Method getMtu) {
|
|
||||||
this.getMtu = getMtu;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public int getMtu(Socket socket) throws IOException {
|
|
||||||
try {
|
|
||||||
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(
|
|
||||||
socket.getLocalAddress());
|
|
||||||
return (Integer) getMtu.invoke(networkInterface);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
|
|
||||||
throw new RuntimeException(e.getCause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Android version 2.3 and newer support TLS session tickets and server name
|
|
||||||
* indication (SNI).
|
|
||||||
*/
|
|
||||||
private static class Android23 extends Java5 {
|
|
||||||
protected final Class<?> openSslSocketClass;
|
protected final Class<?> openSslSocketClass;
|
||||||
private final Method setUseSessionTickets;
|
private final Method setUseSessionTickets;
|
||||||
private final Method setHostname;
|
private final Method setHostname;
|
||||||
|
|
||||||
private Android23(Method getMtu, Class<?> openSslSocketClass, Method setUseSessionTickets,
|
private Android23(
|
||||||
Method setHostname) {
|
Class<?> openSslSocketClass, Method setUseSessionTickets, Method setHostname) {
|
||||||
super(getMtu);
|
|
||||||
this.openSslSocketClass = openSslSocketClass;
|
this.openSslSocketClass = openSslSocketClass;
|
||||||
this.setUseSessionTickets = setUseSessionTickets;
|
this.setUseSessionTickets = setUseSessionTickets;
|
||||||
this.setHostname = setHostname;
|
this.setHostname = setHostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public void connectSocket(Socket socket, InetSocketAddress address,
|
||||||
|
int connectTimeout) throws IOException {
|
||||||
|
try {
|
||||||
|
socket.connect(address, connectTimeout);
|
||||||
|
} catch (SecurityException se) {
|
||||||
|
// Before android 4.3, socket.connect could throw a SecurityException
|
||||||
|
// if opening a socket resulted in an EACCES error.
|
||||||
|
IOException ioException = new IOException("Exception in connect");
|
||||||
|
ioException.initCause(se);
|
||||||
|
throw ioException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void enableTlsExtensions(SSLSocket socket, String uriHost) {
|
@Override public void enableTlsExtensions(SSLSocket socket, String uriHost) {
|
||||||
super.enableTlsExtensions(socket, uriHost);
|
super.enableTlsExtensions(socket, uriHost);
|
||||||
if (openSslSocketClass.isInstance(socket)) {
|
if (openSslSocketClass.isInstance(socket)) {
|
||||||
@@ -249,9 +234,9 @@ public class Platform {
|
|||||||
private final Method setNpnProtocols;
|
private final Method setNpnProtocols;
|
||||||
private final Method getNpnSelectedProtocol;
|
private final Method getNpnSelectedProtocol;
|
||||||
|
|
||||||
private Android41(Method getMtu, Class<?> openSslSocketClass, Method setUseSessionTickets,
|
private Android41(Class<?> openSslSocketClass, Method setUseSessionTickets, Method setHostname,
|
||||||
Method setHostname, Method setNpnProtocols, Method getNpnSelectedProtocol) {
|
Method setNpnProtocols, Method getNpnSelectedProtocol) {
|
||||||
super(getMtu, openSslSocketClass, setUseSessionTickets, setHostname);
|
super(openSslSocketClass, setUseSessionTickets, setHostname);
|
||||||
this.setNpnProtocols = setNpnProtocols;
|
this.setNpnProtocols = setNpnProtocols;
|
||||||
this.getNpnSelectedProtocol = getNpnSelectedProtocol;
|
this.getNpnSelectedProtocol = getNpnSelectedProtocol;
|
||||||
}
|
}
|
||||||
@@ -283,19 +268,15 @@ public class Platform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** OpenJDK 7 plus {@code org.mortbay.jetty.npn/npn-boot} on the boot class path. */
|
||||||
* OpenJDK 7 plus {@code org.mortbay.jetty.npn/npn-boot} on the boot class
|
private static class JdkWithJettyNpnPlatform extends Platform {
|
||||||
* path.
|
|
||||||
*/
|
|
||||||
private static class JdkWithJettyNpnPlatform extends Java5 {
|
|
||||||
private final Method getMethod;
|
private final Method getMethod;
|
||||||
private final Method putMethod;
|
private final Method putMethod;
|
||||||
private final Class<?> clientProviderClass;
|
private final Class<?> clientProviderClass;
|
||||||
private final Class<?> serverProviderClass;
|
private final Class<?> serverProviderClass;
|
||||||
|
|
||||||
public JdkWithJettyNpnPlatform(Method getMtu, Method putMethod, Method getMethod,
|
public JdkWithJettyNpnPlatform(Method putMethod, Method getMethod, Class<?> clientProviderClass,
|
||||||
Class<?> clientProviderClass, Class<?> serverProviderClass) {
|
Class<?> serverProviderClass) {
|
||||||
super(getMtu);
|
|
||||||
this.putMethod = putMethod;
|
this.putMethod = putMethod;
|
||||||
this.getMethod = getMethod;
|
this.getMethod = getMethod;
|
||||||
this.clientProviderClass = clientProviderClass;
|
this.clientProviderClass = clientProviderClass;
|
||||||
@@ -328,7 +309,7 @@ public class Platform {
|
|||||||
JettyNpnProvider provider =
|
JettyNpnProvider provider =
|
||||||
(JettyNpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
|
(JettyNpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
|
||||||
if (!provider.unsupported && provider.selected == null) {
|
if (!provider.unsupported && provider.selected == null) {
|
||||||
Logger logger = Logger.getLogger(OkHttpClient.class.getName());
|
Logger logger = Logger.getLogger("com.squareup.okhttp.OkHttpClient");
|
||||||
logger.log(Level.INFO,
|
logger.log(Level.INFO,
|
||||||
"NPN callback dropped so SPDY is disabled. " + "Is npn-boot on the boot class path?");
|
"NPN callback dropped so SPDY is disabled. " + "Is npn-boot on the boot class path?");
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
3
framework/src/com/squareup/okhttp/internal/StrictLineReader.java
Normal file → Executable file
@@ -146,8 +146,7 @@ public class StrictLineReader implements Closeable {
|
|||||||
|
|
||||||
// Let's anticipate up to 80 characters on top of those already read.
|
// Let's anticipate up to 80 characters on top of those already read.
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
|
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
|
||||||
@Override
|
@Override public String toString() {
|
||||||
public String toString() {
|
|
||||||
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
|
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
|
||||||
try {
|
try {
|
||||||
return new String(buf, 0, length, charset.name());
|
return new String(buf, 0, length, charset.name());
|
||||||
|
|||||||
67
framework/src/com/squareup/okhttp/internal/Util.java
Normal file → Executable file
@@ -24,11 +24,19 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.net.ServerSocket;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/** Junk drawer of utility methods. */
|
/** Junk drawer of utility methods. */
|
||||||
@@ -46,6 +54,9 @@ public final class Util {
|
|||||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||||
private static AtomicReference<byte[]> skipBuffer = new AtomicReference<byte[]>();
|
private static AtomicReference<byte[]> skipBuffer = new AtomicReference<byte[]>();
|
||||||
|
|
||||||
|
private static final char[] DIGITS =
|
||||||
|
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||||
|
|
||||||
private Util() {
|
private Util() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +137,21 @@ public final class Util {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes {@code serverSocket}, ignoring any checked exceptions. Does nothing if
|
||||||
|
* {@code serverSocket} is null.
|
||||||
|
*/
|
||||||
|
public static void closeQuietly(ServerSocket serverSocket) {
|
||||||
|
if (serverSocket != null) {
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (RuntimeException rethrown) {
|
||||||
|
throw rethrown;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes {@code a} and {@code b}. If either close fails, this completes
|
* Closes {@code a} and {@code b}. If either close fails, this completes
|
||||||
* the other close and rethrows the first encountered exception.
|
* the other close and rethrows the first encountered exception.
|
||||||
@@ -258,6 +284,8 @@ public final class Util {
|
|||||||
* buffer.
|
* buffer.
|
||||||
*/
|
*/
|
||||||
public static long skipByReading(InputStream in, long byteCount) throws IOException {
|
public static long skipByReading(InputStream in, long byteCount) throws IOException {
|
||||||
|
if (byteCount == 0) return 0L;
|
||||||
|
|
||||||
// acquire the shared skip buffer.
|
// acquire the shared skip buffer.
|
||||||
byte[] buffer = skipBuffer.getAndSet(null);
|
byte[] buffer = skipBuffer.getAndSet(null);
|
||||||
if (buffer == null) {
|
if (buffer == null) {
|
||||||
@@ -324,4 +352,43 @@ public final class Util {
|
|||||||
}
|
}
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns a 32 character string containing a hash of {@code s}. */
|
||||||
|
public static String hash(String s) {
|
||||||
|
try {
|
||||||
|
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8"));
|
||||||
|
return bytesToHexString(md5bytes);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String bytesToHexString(byte[] bytes) {
|
||||||
|
char[] digits = DIGITS;
|
||||||
|
char[] buf = new char[bytes.length * 2];
|
||||||
|
int c = 0;
|
||||||
|
for (byte b : bytes) {
|
||||||
|
buf[c++] = digits[(b >> 4) & 0xf];
|
||||||
|
buf[c++] = digits[b & 0xf];
|
||||||
|
}
|
||||||
|
return new String(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns an immutable copy of {@code list}. */
|
||||||
|
public static <T> List<T> immutableList(List<T> list) {
|
||||||
|
return Collections.unmodifiableList(new ArrayList<T>(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ThreadFactory daemonThreadFactory(final String name) {
|
||||||
|
return new ThreadFactory() {
|
||||||
|
@Override public Thread newThread(Runnable runnable) {
|
||||||
|
Thread result = new Thread(runnable, name);
|
||||||
|
result.setDaemon(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
framework/src/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
Normal file → Executable file
@@ -79,11 +79,11 @@ abstract class AbstractHttpInputStream extends InputStream {
|
|||||||
* Closes the cache entry and makes the socket available for reuse. This
|
* Closes the cache entry and makes the socket available for reuse. This
|
||||||
* should be invoked when the end of the body has been reached.
|
* should be invoked when the end of the body has been reached.
|
||||||
*/
|
*/
|
||||||
protected final void endOfInput(boolean streamCancelled) throws IOException {
|
protected final void endOfInput() throws IOException {
|
||||||
if (cacheRequest != null) {
|
if (cacheRequest != null) {
|
||||||
cacheBody.close();
|
cacheBody.close();
|
||||||
}
|
}
|
||||||
httpEngine.release(streamCancelled);
|
httpEngine.release(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* 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 com.squareup.okhttp.internal.http;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An output stream for the body of an HTTP request.
|
|
||||||
*
|
|
||||||
* <p>Since a single socket's output stream may be used to write multiple HTTP
|
|
||||||
* requests to the same server, subclasses should not close the socket stream.
|
|
||||||
*/
|
|
||||||
abstract class AbstractHttpOutputStream extends OutputStream {
|
|
||||||
protected boolean closed;
|
|
||||||
|
|
||||||
@Override public final void write(int data) throws IOException {
|
|
||||||
write(new byte[] { (byte) data });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final void checkNotClosed() throws IOException {
|
|
||||||
if (closed) {
|
|
||||||
throw new IOException("stream closed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
8
framework/src/com/squareup/okhttp/internal/http/HeaderParser.java
Normal file → Executable file
@@ -27,11 +27,11 @@ final class HeaderParser {
|
|||||||
int pos = 0;
|
int pos = 0;
|
||||||
while (pos < value.length()) {
|
while (pos < value.length()) {
|
||||||
int tokenStart = pos;
|
int tokenStart = pos;
|
||||||
pos = skipUntil(value, pos, "=,");
|
pos = skipUntil(value, pos, "=,;");
|
||||||
String directive = value.substring(tokenStart, pos).trim();
|
String directive = value.substring(tokenStart, pos).trim();
|
||||||
|
|
||||||
if (pos == value.length() || value.charAt(pos) == ',') {
|
if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
|
||||||
pos++; // consume ',' (if necessary)
|
pos++; // consume ',' or ';' (if necessary)
|
||||||
handler.handle(directive, null);
|
handler.handle(directive, null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ final class HeaderParser {
|
|||||||
// unquoted string
|
// unquoted string
|
||||||
} else {
|
} else {
|
||||||
int parameterStart = pos;
|
int parameterStart = pos;
|
||||||
pos = skipUntil(value, pos, ",");
|
pos = skipUntil(value, pos, ",;");
|
||||||
parameter = value.substring(parameterStart, pos).trim();
|
parameter = value.substring(parameterStart, pos).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
153
framework/src/com/squareup/okhttp/internal/http/HttpAuthenticator.java
Normal file → Executable file
@@ -16,7 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.okhttp.internal.http;
|
package com.squareup.okhttp.internal.http;
|
||||||
|
|
||||||
import com.squareup.okhttp.internal.Base64;
|
import com.squareup.okhttp.OkAuthenticator;
|
||||||
|
import com.squareup.okhttp.OkAuthenticator.Challenge;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Authenticator;
|
import java.net.Authenticator;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@@ -27,11 +28,57 @@ import java.net.URL;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.OkAuthenticator.Credential;
|
||||||
import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
|
import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
|
||||||
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
|
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
|
||||||
|
|
||||||
/** Handles HTTP authentication headers from origin and proxy servers. */
|
/** Handles HTTP authentication headers from origin and proxy servers. */
|
||||||
public final class HttpAuthenticator {
|
public final class HttpAuthenticator {
|
||||||
|
/** Uses the global authenticator to get the password. */
|
||||||
|
public static final OkAuthenticator SYSTEM_DEFAULT = new OkAuthenticator() {
|
||||||
|
@Override public Credential authenticate(
|
||||||
|
Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
|
||||||
|
for (Challenge challenge : challenges) {
|
||||||
|
if (!"Basic".equalsIgnoreCase(challenge.getScheme())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(url.getHost(),
|
||||||
|
getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(),
|
||||||
|
challenge.getRealm(), challenge.getScheme(), url, Authenticator.RequestorType.SERVER);
|
||||||
|
if (auth != null) {
|
||||||
|
return Credential.basic(auth.getUserName(), new String(auth.getPassword()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Credential authenticateProxy(
|
||||||
|
Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
|
||||||
|
for (Challenge challenge : challenges) {
|
||||||
|
if (!"Basic".equalsIgnoreCase(challenge.getScheme())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
|
||||||
|
PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(
|
||||||
|
proxyAddress.getHostName(), getConnectToInetAddress(proxy, url), proxyAddress.getPort(),
|
||||||
|
url.getProtocol(), challenge.getRealm(), challenge.getScheme(), url,
|
||||||
|
Authenticator.RequestorType.PROXY);
|
||||||
|
if (auth != null) {
|
||||||
|
return Credential.basic(auth.getUserName(), new String(auth.getPassword()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException {
|
||||||
|
return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
|
||||||
|
? ((InetSocketAddress) proxy.address()).getAddress()
|
||||||
|
: InetAddress.getByName(url.getHost());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private HttpAuthenticator() {
|
private HttpAuthenticator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,68 +88,33 @@ public final class HttpAuthenticator {
|
|||||||
* @return true if credentials have been added to successorRequestHeaders
|
* @return true if credentials have been added to successorRequestHeaders
|
||||||
* and another request should be attempted.
|
* and another request should be attempted.
|
||||||
*/
|
*/
|
||||||
public static boolean processAuthHeader(int responseCode, RawHeaders responseHeaders,
|
public static boolean processAuthHeader(OkAuthenticator authenticator, int responseCode,
|
||||||
RawHeaders successorRequestHeaders, Proxy proxy, URL url) throws IOException {
|
RawHeaders responseHeaders, RawHeaders successorRequestHeaders, Proxy proxy, URL url)
|
||||||
if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) {
|
throws IOException {
|
||||||
throw new IllegalArgumentException();
|
String responseField;
|
||||||
|
String requestField;
|
||||||
|
if (responseCode == HTTP_UNAUTHORIZED) {
|
||||||
|
responseField = "WWW-Authenticate";
|
||||||
|
requestField = "Authorization";
|
||||||
|
} else if (responseCode == HTTP_PROXY_AUTH) {
|
||||||
|
responseField = "Proxy-Authenticate";
|
||||||
|
requestField = "Proxy-Authorization";
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(); // TODO: ProtocolException?
|
||||||
}
|
}
|
||||||
|
List<Challenge> challenges = parseChallenges(responseHeaders, responseField);
|
||||||
// Keep asking for username/password until authorized.
|
|
||||||
String challengeHeader =
|
|
||||||
responseCode == HTTP_PROXY_AUTH ? "Proxy-Authenticate" : "WWW-Authenticate";
|
|
||||||
String credentials = getCredentials(responseHeaders, challengeHeader, proxy, url);
|
|
||||||
if (credentials == null) {
|
|
||||||
return false; // Could not find credentials so end the request cycle.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add authorization credentials, bypassing the already-connected check.
|
|
||||||
String fieldName = responseCode == HTTP_PROXY_AUTH ? "Proxy-Authorization" : "Authorization";
|
|
||||||
successorRequestHeaders.set(fieldName, credentials);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the authorization credentials that may satisfy the challenge.
|
|
||||||
* Returns null if a challenge header was not provided or if credentials
|
|
||||||
* were not available.
|
|
||||||
*/
|
|
||||||
private static String getCredentials(RawHeaders responseHeaders, String challengeHeader,
|
|
||||||
Proxy proxy, URL url) throws IOException {
|
|
||||||
List<Challenge> challenges = parseChallenges(responseHeaders, challengeHeader);
|
|
||||||
if (challenges.isEmpty()) {
|
if (challenges.isEmpty()) {
|
||||||
return null;
|
return false; // Could not find a challenge so end the request cycle.
|
||||||
}
|
}
|
||||||
|
Credential credential = responseHeaders.getResponseCode() == HTTP_PROXY_AUTH
|
||||||
for (Challenge challenge : challenges) {
|
? authenticator.authenticateProxy(proxy, url, challenges)
|
||||||
// Use the global authenticator to get the password.
|
: authenticator.authenticate(proxy, url, challenges);
|
||||||
PasswordAuthentication auth;
|
if (credential == null) {
|
||||||
if (responseHeaders.getResponseCode() == HTTP_PROXY_AUTH) {
|
return false; // Could not satisfy the challenge so end the request cycle.
|
||||||
InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
|
|
||||||
auth = Authenticator.requestPasswordAuthentication(proxyAddress.getHostName(),
|
|
||||||
getConnectToInetAddress(proxy, url), proxyAddress.getPort(), url.getProtocol(),
|
|
||||||
challenge.realm, challenge.scheme, url, Authenticator.RequestorType.PROXY);
|
|
||||||
} else {
|
|
||||||
auth = Authenticator.requestPasswordAuthentication(url.getHost(),
|
|
||||||
getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(), challenge.realm,
|
|
||||||
challenge.scheme, url, Authenticator.RequestorType.SERVER);
|
|
||||||
}
|
|
||||||
if (auth == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use base64 to encode the username and password.
|
|
||||||
String usernameAndPassword = auth.getUserName() + ":" + new String(auth.getPassword());
|
|
||||||
byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
|
|
||||||
String encoded = Base64.encode(bytes);
|
|
||||||
return challenge.scheme + " " + encoded;
|
|
||||||
}
|
}
|
||||||
|
// Add authorization credentials, bypassing the already-connected check.
|
||||||
return null;
|
successorRequestHeaders.set(requestField, credential.getHeaderValue());
|
||||||
}
|
return true;
|
||||||
|
|
||||||
private static InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException {
|
|
||||||
return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
|
|
||||||
? ((InetSocketAddress) proxy.address()).getAddress() : InetAddress.getByName(url.getHost());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,7 +146,7 @@ public final class HttpAuthenticator {
|
|||||||
// It needs to be fixed to handle any scheme and any parameters
|
// It needs to be fixed to handle any scheme and any parameters
|
||||||
// http://code.google.com/p/android/issues/detail?id=11140
|
// http://code.google.com/p/android/issues/detail?id=11140
|
||||||
|
|
||||||
if (!value.regionMatches(pos, "realm=\"", 0, "realm=\"".length())) {
|
if (!value.regionMatches(true, pos, "realm=\"", 0, "realm=\"".length())) {
|
||||||
break; // Unexpected challenge parameter; give up!
|
break; // Unexpected challenge parameter; give up!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,25 +163,4 @@ public final class HttpAuthenticator {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An RFC 2617 challenge. */
|
|
||||||
private static final class Challenge {
|
|
||||||
final String scheme;
|
|
||||||
final String realm;
|
|
||||||
|
|
||||||
Challenge(String scheme, String realm) {
|
|
||||||
this.scheme = scheme;
|
|
||||||
this.realm = realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public boolean equals(Object o) {
|
|
||||||
return o instanceof Challenge
|
|
||||||
&& ((Challenge) o).scheme.equals(scheme)
|
|
||||||
&& ((Challenge) o).realm.equals(realm);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public int hashCode() {
|
|
||||||
return scheme.hashCode() + 31 * realm.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
30
framework/src/com/squareup/okhttp/internal/http/HttpDate.java
Normal file → Executable file
@@ -36,14 +36,13 @@ final class HttpDate {
|
|||||||
new ThreadLocal<DateFormat>() {
|
new ThreadLocal<DateFormat>() {
|
||||||
@Override protected DateFormat initialValue() {
|
@Override protected DateFormat initialValue() {
|
||||||
DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
||||||
rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
|
rfc1123.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||||
return rfc1123;
|
return rfc1123;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** If we fail to parse a date in a non-standard format, try each of these formats in sequence. */
|
/** If we fail to parse a date in a non-standard format, try each of these formats in sequence. */
|
||||||
private static final String[] BROWSER_COMPATIBLE_DATE_FORMATS = new String[] {
|
private static final String[] BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS = new String[] {
|
||||||
/* This list comes from {@code org.apache.http.impl.cookie.BrowserCompatSpec}. */
|
|
||||||
"EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 1036
|
"EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 1036
|
||||||
"EEE MMM d HH:mm:ss yyyy", // ANSI C asctime()
|
"EEE MMM d HH:mm:ss yyyy", // ANSI C asctime()
|
||||||
"EEE, dd-MMM-yyyy HH:mm:ss z", "EEE, dd-MMM-yyyy HH-mm-ss z", "EEE, dd MMM yy HH:mm:ss z",
|
"EEE, dd-MMM-yyyy HH:mm:ss z", "EEE, dd-MMM-yyyy HH-mm-ss z", "EEE, dd MMM yy HH:mm:ss z",
|
||||||
@@ -54,19 +53,26 @@ final class HttpDate {
|
|||||||
/* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */
|
/* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */
|
||||||
"EEE MMM d yyyy HH:mm:ss z", };
|
"EEE MMM d yyyy HH:mm:ss z", };
|
||||||
|
|
||||||
/**
|
private static final DateFormat[] BROWSER_COMPATIBLE_DATE_FORMATS =
|
||||||
* Returns the date for {@code value}. Returns null if the value couldn't be
|
new DateFormat[BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length];
|
||||||
* parsed.
|
|
||||||
*/
|
/** Returns the date for {@code value}. Returns null if the value couldn't be parsed. */
|
||||||
public static Date parse(String value) {
|
public static Date parse(String value) {
|
||||||
try {
|
try {
|
||||||
return STANDARD_DATE_FORMAT.get().parse(value);
|
return STANDARD_DATE_FORMAT.get().parse(value);
|
||||||
} catch (ParseException ignore) {
|
} catch (ParseException ignored) {
|
||||||
}
|
}
|
||||||
for (String formatString : BROWSER_COMPATIBLE_DATE_FORMATS) {
|
synchronized (BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS) {
|
||||||
try {
|
for (int i = 0, count = BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS.length; i < count; i++) {
|
||||||
return new SimpleDateFormat(formatString, Locale.US).parse(value);
|
DateFormat format = BROWSER_COMPATIBLE_DATE_FORMATS[i];
|
||||||
} catch (ParseException ignore) {
|
if (format == null) {
|
||||||
|
format = new SimpleDateFormat(BROWSER_COMPATIBLE_DATE_FORMAT_STRINGS[i], Locale.US);
|
||||||
|
BROWSER_COMPATIBLE_DATE_FORMATS[i] = format;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return format.parse(value);
|
||||||
|
} catch (ParseException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
108
framework/src/com/squareup/okhttp/internal/http/HttpEngine.java
Normal file → Executable file
@@ -19,6 +19,8 @@ package com.squareup.okhttp.internal.http;
|
|||||||
|
|
||||||
import com.squareup.okhttp.Address;
|
import com.squareup.okhttp.Address;
|
||||||
import com.squareup.okhttp.Connection;
|
import com.squareup.okhttp.Connection;
|
||||||
|
import com.squareup.okhttp.OkHttpClient;
|
||||||
|
import com.squareup.okhttp.OkResponseCache;
|
||||||
import com.squareup.okhttp.ResponseSource;
|
import com.squareup.okhttp.ResponseSource;
|
||||||
import com.squareup.okhttp.TunnelRequest;
|
import com.squareup.okhttp.TunnelRequest;
|
||||||
import com.squareup.okhttp.internal.Dns;
|
import com.squareup.okhttp.internal.Dns;
|
||||||
@@ -31,6 +33,7 @@ import java.io.OutputStream;
|
|||||||
import java.net.CacheRequest;
|
import java.net.CacheRequest;
|
||||||
import java.net.CacheResponse;
|
import java.net.CacheResponse;
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
@@ -85,7 +88,8 @@ public class HttpEngine {
|
|||||||
};
|
};
|
||||||
public static final int HTTP_CONTINUE = 100;
|
public static final int HTTP_CONTINUE = 100;
|
||||||
|
|
||||||
protected final HttpURLConnectionImpl policy;
|
protected final Policy policy;
|
||||||
|
protected final OkHttpClient client;
|
||||||
|
|
||||||
protected final String method;
|
protected final String method;
|
||||||
|
|
||||||
@@ -106,6 +110,9 @@ public class HttpEngine {
|
|||||||
/** The time when the request headers were written, or -1 if they haven't been written yet. */
|
/** The time when the request headers were written, or -1 if they haven't been written yet. */
|
||||||
long sentRequestMillis = -1;
|
long sentRequestMillis = -1;
|
||||||
|
|
||||||
|
/** Whether the connection has been established. */
|
||||||
|
boolean connected;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if this client added an "Accept-Encoding: gzip" header field and is
|
* True if this client added an "Accept-Encoding: gzip" header field and is
|
||||||
* therefore responsible for also decompressing the transfer stream.
|
* therefore responsible for also decompressing the transfer stream.
|
||||||
@@ -137,14 +144,15 @@ public class HttpEngine {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param requestHeaders the client's supplied request headers. This class
|
* @param requestHeaders the client's supplied request headers. This class
|
||||||
* creates a private copy that it can mutate.
|
* creates a private copy that it can mutate.
|
||||||
* @param connection the connection used for an intermediate response
|
* @param connection the connection used for an intermediate response
|
||||||
* immediately prior to this request/response pair, such as a same-host
|
* immediately prior to this request/response pair, such as a same-host
|
||||||
* redirect. This engine assumes ownership of the connection and must
|
* redirect. This engine assumes ownership of the connection and must
|
||||||
* release it when it is unneeded.
|
* release it when it is unneeded.
|
||||||
*/
|
*/
|
||||||
public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
|
public HttpEngine(OkHttpClient client, Policy policy, String method, RawHeaders requestHeaders,
|
||||||
Connection connection, RetryableOutputStream requestBodyOut) throws IOException {
|
Connection connection, RetryableOutputStream requestBodyOut) throws IOException {
|
||||||
|
this.client = client;
|
||||||
this.policy = policy;
|
this.policy = policy;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
@@ -175,8 +183,9 @@ public class HttpEngine {
|
|||||||
|
|
||||||
prepareRawRequestHeaders();
|
prepareRawRequestHeaders();
|
||||||
initResponseSource();
|
initResponseSource();
|
||||||
if (policy.responseCache != null) {
|
OkResponseCache responseCache = client.getOkResponseCache();
|
||||||
policy.responseCache.trackResponse(responseSource);
|
if (responseCache != null) {
|
||||||
|
responseCache.trackResponse(responseSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The raw response source may require the network, but the request
|
// The raw response source may require the network, but the request
|
||||||
@@ -196,8 +205,7 @@ public class HttpEngine {
|
|||||||
if (responseSource.requiresConnection()) {
|
if (responseSource.requiresConnection()) {
|
||||||
sendSocketRequest();
|
sendSocketRequest();
|
||||||
} else if (connection != null) {
|
} else if (connection != null) {
|
||||||
policy.connectionPool.recycle(connection);
|
client.getConnectionPool().recycle(connection);
|
||||||
policy.getFailedRoutes().remove(connection.getRoute());
|
|
||||||
connection = null;
|
connection = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,15 +216,14 @@ public class HttpEngine {
|
|||||||
*/
|
*/
|
||||||
private void initResponseSource() throws IOException {
|
private void initResponseSource() throws IOException {
|
||||||
responseSource = ResponseSource.NETWORK;
|
responseSource = ResponseSource.NETWORK;
|
||||||
if (!policy.getUseCaches() || policy.responseCache == null) {
|
if (!policy.getUseCaches()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CacheResponse candidate =
|
OkResponseCache responseCache = client.getOkResponseCache();
|
||||||
policy.responseCache.get(uri, method, requestHeaders.getHeaders().toMultimap(false));
|
if (responseCache == null) return;
|
||||||
if (candidate == null) {
|
|
||||||
return;
|
CacheResponse candidate = responseCache.get(
|
||||||
}
|
uri, method, requestHeaders.getHeaders().toMultimap(false));
|
||||||
|
if (candidate == null) return;
|
||||||
|
|
||||||
Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
|
Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
|
||||||
cachedResponseBody = candidate.getBody();
|
cachedResponseBody = candidate.getBody();
|
||||||
@@ -274,22 +281,24 @@ public class HttpEngine {
|
|||||||
SSLSocketFactory sslSocketFactory = null;
|
SSLSocketFactory sslSocketFactory = null;
|
||||||
HostnameVerifier hostnameVerifier = null;
|
HostnameVerifier hostnameVerifier = null;
|
||||||
if (uri.getScheme().equalsIgnoreCase("https")) {
|
if (uri.getScheme().equalsIgnoreCase("https")) {
|
||||||
sslSocketFactory = policy.sslSocketFactory;
|
sslSocketFactory = client.getSslSocketFactory();
|
||||||
hostnameVerifier = policy.hostnameVerifier;
|
hostnameVerifier = client.getHostnameVerifier();
|
||||||
}
|
}
|
||||||
Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
|
Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
|
||||||
hostnameVerifier, policy.requestedProxy);
|
hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getTransports());
|
||||||
routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool,
|
routeSelector = new RouteSelector(address, uri, client.getProxySelector(),
|
||||||
Dns.DEFAULT, policy.getFailedRoutes());
|
client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());
|
||||||
}
|
}
|
||||||
connection = routeSelector.next();
|
connection = routeSelector.next(method);
|
||||||
if (!connection.isConnected()) {
|
if (!connection.isConnected()) {
|
||||||
connection.connect(policy.getConnectTimeout(), policy.getReadTimeout(), getTunnelConfig());
|
connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig());
|
||||||
policy.connectionPool.maybeShare(connection);
|
client.getConnectionPool().maybeShare(connection);
|
||||||
policy.getFailedRoutes().remove(connection.getRoute());
|
client.getRoutesDatabase().connected(connection.getRoute());
|
||||||
|
} else if (!connection.isSpdy()) {
|
||||||
|
connection.updateReadTimeout(client.getReadTimeout());
|
||||||
}
|
}
|
||||||
connected(connection);
|
connected(connection);
|
||||||
if (connection.getRoute().getProxy() != policy.requestedProxy) {
|
if (connection.getRoute().getProxy() != client.getProxy()) {
|
||||||
// Update the request line if the proxy changed; it may need a host name.
|
// Update the request line if the proxy changed; it may need a host name.
|
||||||
requestHeaders.getHeaders().setRequestLine(getRequestLine());
|
requestHeaders.getHeaders().setRequestLine(getRequestLine());
|
||||||
}
|
}
|
||||||
@@ -300,6 +309,8 @@ public class HttpEngine {
|
|||||||
* pool. Subclasses use this hook to get a reference to the TLS data.
|
* pool. Subclasses use this hook to get a reference to the TLS data.
|
||||||
*/
|
*/
|
||||||
protected void connected(Connection connection) {
|
protected void connected(Connection connection) {
|
||||||
|
policy.setSelectedProxy(connection.getRoute().getProxy());
|
||||||
|
connected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -328,7 +339,7 @@ public class HttpEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean hasRequestBody() {
|
boolean hasRequestBody() {
|
||||||
return method.equals("POST") || method.equals("PUT");
|
return method.equals("POST") || method.equals("PUT") || method.equals("PATCH");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the request body or null if this request doesn't have a body. */
|
/** Returns the request body or null if this request doesn't have a body. */
|
||||||
@@ -387,17 +398,20 @@ public class HttpEngine {
|
|||||||
|
|
||||||
private void maybeCache() throws IOException {
|
private void maybeCache() throws IOException {
|
||||||
// Are we caching at all?
|
// Are we caching at all?
|
||||||
if (!policy.getUseCaches() || policy.responseCache == null) {
|
if (!policy.getUseCaches()) return;
|
||||||
return;
|
OkResponseCache responseCache = client.getOkResponseCache();
|
||||||
}
|
if (responseCache == null) return;
|
||||||
|
|
||||||
|
HttpURLConnection connectionToCache = policy.getHttpConnectionToCache();
|
||||||
|
|
||||||
// Should we cache this response for this request?
|
// Should we cache this response for this request?
|
||||||
if (!responseHeaders.isCacheable(requestHeaders)) {
|
if (!responseHeaders.isCacheable(requestHeaders)) {
|
||||||
|
responseCache.maybeRemove(connectionToCache.getRequestMethod(), uri);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offer this request to the cache.
|
// Offer this request to the cache.
|
||||||
cacheRequest = policy.responseCache.put(uri, policy.getHttpConnectionToCache());
|
cacheRequest = responseCache.put(uri, connectionToCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -409,7 +423,7 @@ public class HttpEngine {
|
|||||||
public final void automaticallyReleaseConnectionToPool() {
|
public final void automaticallyReleaseConnectionToPool() {
|
||||||
automaticallyReleaseConnectionToPool = true;
|
automaticallyReleaseConnectionToPool = true;
|
||||||
if (connection != null && connectionReleased) {
|
if (connection != null && connectionReleased) {
|
||||||
policy.connectionPool.recycle(connection);
|
client.getConnectionPool().recycle(connection);
|
||||||
connection = null;
|
connection = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,7 +433,7 @@ public class HttpEngine {
|
|||||||
* closed. Also call {@link #automaticallyReleaseConnectionToPool} unless
|
* closed. Also call {@link #automaticallyReleaseConnectionToPool} unless
|
||||||
* the connection will be used to follow a redirect.
|
* the connection will be used to follow a redirect.
|
||||||
*/
|
*/
|
||||||
public final void release(boolean streamCancelled) {
|
public final void release(boolean streamCanceled) {
|
||||||
// If the response body comes from the cache, close it.
|
// If the response body comes from the cache, close it.
|
||||||
if (responseBodyIn == cachedResponseBody) {
|
if (responseBodyIn == cachedResponseBody) {
|
||||||
Util.closeQuietly(responseBodyIn);
|
Util.closeQuietly(responseBodyIn);
|
||||||
@@ -428,12 +442,12 @@ public class HttpEngine {
|
|||||||
if (!connectionReleased && connection != null) {
|
if (!connectionReleased && connection != null) {
|
||||||
connectionReleased = true;
|
connectionReleased = true;
|
||||||
|
|
||||||
if (transport == null || !transport.makeReusable(streamCancelled, requestBodyOut,
|
if (transport == null
|
||||||
responseTransferIn)) {
|
|| !transport.makeReusable(streamCanceled, requestBodyOut, responseTransferIn)) {
|
||||||
Util.closeQuietly(connection);
|
Util.closeQuietly(connection);
|
||||||
connection = null;
|
connection = null;
|
||||||
} else if (automaticallyReleaseConnectionToPool) {
|
} else if (automaticallyReleaseConnectionToPool) {
|
||||||
policy.connectionPool.recycle(connection);
|
client.getConnectionPool().recycle(connection);
|
||||||
connection = null;
|
connection = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -521,7 +535,7 @@ public class HttpEngine {
|
|||||||
requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
|
requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
|
||||||
}
|
}
|
||||||
|
|
||||||
CookieHandler cookieHandler = policy.cookieHandler;
|
CookieHandler cookieHandler = client.getCookieHandler();
|
||||||
if (cookieHandler != null) {
|
if (cookieHandler != null) {
|
||||||
requestHeaders.addCookies(
|
requestHeaders.addCookies(
|
||||||
cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false)));
|
cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false)));
|
||||||
@@ -635,9 +649,17 @@ public class HttpEngine {
|
|||||||
if (cachedResponseHeaders.validate(responseHeaders)) {
|
if (cachedResponseHeaders.validate(responseHeaders)) {
|
||||||
release(false);
|
release(false);
|
||||||
ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
|
ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
|
||||||
setResponse(combinedHeaders, cachedResponseBody);
|
this.responseHeaders = combinedHeaders;
|
||||||
policy.responseCache.trackConditionalCacheHit();
|
|
||||||
policy.responseCache.update(cacheResponse, policy.getHttpConnectionToCache());
|
// Update the cache after applying the combined headers but before initializing the content
|
||||||
|
// stream, otherwise the Content-Encoding header (if present) will be stripped from the
|
||||||
|
// combined headers and not end up in the cache file if transparent gzip compression is
|
||||||
|
// turned on.
|
||||||
|
OkResponseCache responseCache = client.getOkResponseCache();
|
||||||
|
responseCache.trackConditionalCacheHit();
|
||||||
|
responseCache.update(cacheResponse, policy.getHttpConnectionToCache());
|
||||||
|
|
||||||
|
initContentStream(cachedResponseBody);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
Util.closeQuietly(cachedResponseBody);
|
Util.closeQuietly(cachedResponseBody);
|
||||||
@@ -656,7 +678,7 @@ public class HttpEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void receiveHeaders(RawHeaders headers) throws IOException {
|
public void receiveHeaders(RawHeaders headers) throws IOException {
|
||||||
CookieHandler cookieHandler = policy.cookieHandler;
|
CookieHandler cookieHandler = client.getCookieHandler();
|
||||||
if (cookieHandler != null) {
|
if (cookieHandler != null) {
|
||||||
cookieHandler.put(uri, headers.toMultimap(true));
|
cookieHandler.put(uri, headers.toMultimap(true));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,608 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* 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 com.squareup.okhttp.internal.http;
|
|
||||||
|
|
||||||
import com.squareup.okhttp.OkResponseCache;
|
|
||||||
import com.squareup.okhttp.ResponseSource;
|
|
||||||
import com.squareup.okhttp.internal.Base64;
|
|
||||||
import com.squareup.okhttp.internal.DiskLruCache;
|
|
||||||
import com.squareup.okhttp.internal.StrictLineReader;
|
|
||||||
import com.squareup.okhttp.internal.Util;
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FilterInputStream;
|
|
||||||
import java.io.FilterOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.net.CacheRequest;
|
|
||||||
import java.net.CacheResponse;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.ResponseCache;
|
|
||||||
import java.net.SecureCacheResponse;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.security.cert.CertificateEncodingException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
|
||||||
|
|
||||||
import static com.squareup.okhttp.internal.Util.US_ASCII;
|
|
||||||
import static com.squareup.okhttp.internal.Util.UTF_8;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache responses in a directory on the file system. Most clients should use
|
|
||||||
* {@code android.net.HttpResponseCache}, the stable, documented front end for
|
|
||||||
* this.
|
|
||||||
*/
|
|
||||||
public final class HttpResponseCache extends ResponseCache implements OkResponseCache {
|
|
||||||
private static final char[] DIGITS =
|
|
||||||
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
|
||||||
|
|
||||||
// TODO: add APIs to iterate the cache?
|
|
||||||
private static final int VERSION = 201105;
|
|
||||||
private static final int ENTRY_METADATA = 0;
|
|
||||||
private static final int ENTRY_BODY = 1;
|
|
||||||
private static final int ENTRY_COUNT = 2;
|
|
||||||
|
|
||||||
private final DiskLruCache cache;
|
|
||||||
|
|
||||||
/* read and write statistics, all guarded by 'this' */
|
|
||||||
private int writeSuccessCount;
|
|
||||||
private int writeAbortCount;
|
|
||||||
private int networkCount;
|
|
||||||
private int hitCount;
|
|
||||||
private int requestCount;
|
|
||||||
|
|
||||||
public HttpResponseCache(File directory, long maxSize) throws IOException {
|
|
||||||
cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String uriToKey(URI uri) {
|
|
||||||
try {
|
|
||||||
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
|
||||||
byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8"));
|
|
||||||
return bytesToHexString(md5bytes);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String bytesToHexString(byte[] bytes) {
|
|
||||||
char[] digits = DIGITS;
|
|
||||||
char[] buf = new char[bytes.length * 2];
|
|
||||||
int c = 0;
|
|
||||||
for (byte b : bytes) {
|
|
||||||
buf[c++] = digits[(b >> 4) & 0xf];
|
|
||||||
buf[c++] = digits[b & 0xf];
|
|
||||||
}
|
|
||||||
return new String(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public CacheResponse get(URI uri, String requestMethod,
|
|
||||||
Map<String, List<String>> requestHeaders) {
|
|
||||||
String key = uriToKey(uri);
|
|
||||||
DiskLruCache.Snapshot snapshot;
|
|
||||||
Entry entry;
|
|
||||||
try {
|
|
||||||
snapshot = cache.get(key);
|
|
||||||
if (snapshot == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
entry = new Entry(snapshot.getInputStream(ENTRY_METADATA));
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Give up because the cache cannot be read.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entry.matches(uri, requestMethod, requestHeaders)) {
|
|
||||||
snapshot.close();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry.isHttps() ? new EntrySecureCacheResponse(entry, snapshot)
|
|
||||||
: new EntryCacheResponse(entry, snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
|
|
||||||
if (!(urlConnection instanceof HttpURLConnection)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
|
|
||||||
String requestMethod = httpConnection.getRequestMethod();
|
|
||||||
String key = uriToKey(uri);
|
|
||||||
|
|
||||||
if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals(
|
|
||||||
"DELETE")) {
|
|
||||||
try {
|
|
||||||
cache.remove(key);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
// The cache cannot be written.
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} else if (!requestMethod.equals("GET")) {
|
|
||||||
// Don't cache non-GET responses. We're technically allowed to cache
|
|
||||||
// HEAD requests and some POST requests, but the complexity of doing
|
|
||||||
// so is high and the benefit is low.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpEngine httpEngine = getHttpEngine(httpConnection);
|
|
||||||
if (httpEngine == null) {
|
|
||||||
// Don't cache unless the HTTP implementation is ours.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponseHeaders response = httpEngine.getResponseHeaders();
|
|
||||||
if (response.hasVaryAll()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
RawHeaders varyHeaders =
|
|
||||||
httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields());
|
|
||||||
Entry entry = new Entry(uri, varyHeaders, httpConnection);
|
|
||||||
DiskLruCache.Editor editor = null;
|
|
||||||
try {
|
|
||||||
editor = cache.edit(key);
|
|
||||||
if (editor == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
entry.writeTo(editor);
|
|
||||||
return new CacheRequestImpl(editor);
|
|
||||||
} catch (IOException e) {
|
|
||||||
abortQuietly(editor);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a conditional request hit by updating the stored cache response
|
|
||||||
* with the headers from {@code httpConnection}. The cached response body is
|
|
||||||
* not updated. If the stored response has changed since {@code
|
|
||||||
* conditionalCacheHit} was returned, this does nothing.
|
|
||||||
*/
|
|
||||||
@Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
|
|
||||||
throws IOException {
|
|
||||||
HttpEngine httpEngine = getHttpEngine(httpConnection);
|
|
||||||
URI uri = httpEngine.getUri();
|
|
||||||
ResponseHeaders response = httpEngine.getResponseHeaders();
|
|
||||||
RawHeaders varyHeaders =
|
|
||||||
httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields());
|
|
||||||
Entry entry = new Entry(uri, varyHeaders, httpConnection);
|
|
||||||
DiskLruCache.Snapshot snapshot = (conditionalCacheHit instanceof EntryCacheResponse)
|
|
||||||
? ((EntryCacheResponse) conditionalCacheHit).snapshot
|
|
||||||
: ((EntrySecureCacheResponse) conditionalCacheHit).snapshot;
|
|
||||||
DiskLruCache.Editor editor = null;
|
|
||||||
try {
|
|
||||||
editor = snapshot.edit(); // returns null if snapshot is not current
|
|
||||||
if (editor != null) {
|
|
||||||
entry.writeTo(editor);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
abortQuietly(editor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void abortQuietly(DiskLruCache.Editor editor) {
|
|
||||||
// Give up because the cache cannot be written.
|
|
||||||
try {
|
|
||||||
if (editor != null) {
|
|
||||||
editor.abort();
|
|
||||||
}
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpEngine getHttpEngine(URLConnection httpConnection) {
|
|
||||||
if (httpConnection instanceof HttpURLConnectionImpl) {
|
|
||||||
return ((HttpURLConnectionImpl) httpConnection).getHttpEngine();
|
|
||||||
} else if (httpConnection instanceof HttpsURLConnectionImpl) {
|
|
||||||
return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiskLruCache getCache() {
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized int getWriteAbortCount() {
|
|
||||||
return writeAbortCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized int getWriteSuccessCount() {
|
|
||||||
return writeSuccessCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void trackResponse(ResponseSource source) {
|
|
||||||
requestCount++;
|
|
||||||
|
|
||||||
switch (source) {
|
|
||||||
case CACHE:
|
|
||||||
hitCount++;
|
|
||||||
break;
|
|
||||||
case CONDITIONAL_CACHE:
|
|
||||||
case NETWORK:
|
|
||||||
networkCount++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void trackConditionalCacheHit() {
|
|
||||||
hitCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized int getNetworkCount() {
|
|
||||||
return networkCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized int getHitCount() {
|
|
||||||
return hitCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized int getRequestCount() {
|
|
||||||
return requestCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class CacheRequestImpl extends CacheRequest {
|
|
||||||
private final DiskLruCache.Editor editor;
|
|
||||||
private OutputStream cacheOut;
|
|
||||||
private boolean done;
|
|
||||||
private OutputStream body;
|
|
||||||
|
|
||||||
public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
|
|
||||||
this.editor = editor;
|
|
||||||
this.cacheOut = editor.newOutputStream(ENTRY_BODY);
|
|
||||||
this.body = new FilterOutputStream(cacheOut) {
|
|
||||||
@Override public void close() throws IOException {
|
|
||||||
synchronized (HttpResponseCache.this) {
|
|
||||||
if (done) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
done = true;
|
|
||||||
writeSuccessCount++;
|
|
||||||
}
|
|
||||||
super.close();
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(byte[] buffer, int offset, int length) throws IOException {
|
|
||||||
// Since we don't override "write(int oneByte)", we can write directly to "out"
|
|
||||||
// and avoid the inefficient implementation from the FilterOutputStream.
|
|
||||||
out.write(buffer, offset, length);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void abort() {
|
|
||||||
synchronized (HttpResponseCache.this) {
|
|
||||||
if (done) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
done = true;
|
|
||||||
writeAbortCount++;
|
|
||||||
}
|
|
||||||
Util.closeQuietly(cacheOut);
|
|
||||||
try {
|
|
||||||
editor.abort();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public OutputStream getBody() throws IOException {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Entry {
|
|
||||||
private final String uri;
|
|
||||||
private final RawHeaders varyHeaders;
|
|
||||||
private final String requestMethod;
|
|
||||||
private final RawHeaders responseHeaders;
|
|
||||||
private final String cipherSuite;
|
|
||||||
private final Certificate[] peerCertificates;
|
|
||||||
private final Certificate[] localCertificates;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads an entry from an input stream. A typical entry looks like this:
|
|
||||||
* <pre>{@code
|
|
||||||
* http://google.com/foo
|
|
||||||
* GET
|
|
||||||
* 2
|
|
||||||
* Accept-Language: fr-CA
|
|
||||||
* Accept-Charset: UTF-8
|
|
||||||
* HTTP/1.1 200 OK
|
|
||||||
* 3
|
|
||||||
* Content-Type: image/png
|
|
||||||
* Content-Length: 100
|
|
||||||
* Cache-Control: max-age=600
|
|
||||||
* }</pre>
|
|
||||||
*
|
|
||||||
* <p>A typical HTTPS file looks like this:
|
|
||||||
* <pre>{@code
|
|
||||||
* https://google.com/foo
|
|
||||||
* GET
|
|
||||||
* 2
|
|
||||||
* Accept-Language: fr-CA
|
|
||||||
* Accept-Charset: UTF-8
|
|
||||||
* HTTP/1.1 200 OK
|
|
||||||
* 3
|
|
||||||
* Content-Type: image/png
|
|
||||||
* Content-Length: 100
|
|
||||||
* Cache-Control: max-age=600
|
|
||||||
*
|
|
||||||
* AES_256_WITH_MD5
|
|
||||||
* 2
|
|
||||||
* base64-encoded peerCertificate[0]
|
|
||||||
* base64-encoded peerCertificate[1]
|
|
||||||
* -1
|
|
||||||
* }</pre>
|
|
||||||
* The file is newline separated. The first two lines are the URL and
|
|
||||||
* the request method. Next is the number of HTTP Vary request header
|
|
||||||
* lines, followed by those lines.
|
|
||||||
*
|
|
||||||
* <p>Next is the response status line, followed by the number of HTTP
|
|
||||||
* response header lines, followed by those lines.
|
|
||||||
*
|
|
||||||
* <p>HTTPS responses also contain SSL session information. This begins
|
|
||||||
* with a blank line, and then a line containing the cipher suite. Next
|
|
||||||
* is the length of the peer certificate chain. These certificates are
|
|
||||||
* base64-encoded and appear each on their own line. The next line
|
|
||||||
* contains the length of the local certificate chain. These
|
|
||||||
* certificates are also base64-encoded and appear each on their own
|
|
||||||
* line. A length of -1 is used to encode a null array.
|
|
||||||
*/
|
|
||||||
public Entry(InputStream in) throws IOException {
|
|
||||||
try {
|
|
||||||
StrictLineReader reader = new StrictLineReader(in, US_ASCII);
|
|
||||||
uri = reader.readLine();
|
|
||||||
requestMethod = reader.readLine();
|
|
||||||
varyHeaders = new RawHeaders();
|
|
||||||
int varyRequestHeaderLineCount = reader.readInt();
|
|
||||||
for (int i = 0; i < varyRequestHeaderLineCount; i++) {
|
|
||||||
varyHeaders.addLine(reader.readLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
responseHeaders = new RawHeaders();
|
|
||||||
responseHeaders.setStatusLine(reader.readLine());
|
|
||||||
int responseHeaderLineCount = reader.readInt();
|
|
||||||
for (int i = 0; i < responseHeaderLineCount; i++) {
|
|
||||||
responseHeaders.addLine(reader.readLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isHttps()) {
|
|
||||||
String blank = reader.readLine();
|
|
||||||
if (!blank.isEmpty()) {
|
|
||||||
throw new IOException("expected \"\" but was \"" + blank + "\"");
|
|
||||||
}
|
|
||||||
cipherSuite = reader.readLine();
|
|
||||||
peerCertificates = readCertArray(reader);
|
|
||||||
localCertificates = readCertArray(reader);
|
|
||||||
} else {
|
|
||||||
cipherSuite = null;
|
|
||||||
peerCertificates = null;
|
|
||||||
localCertificates = null;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection)
|
|
||||||
throws IOException {
|
|
||||||
this.uri = uri.toString();
|
|
||||||
this.varyHeaders = varyHeaders;
|
|
||||||
this.requestMethod = httpConnection.getRequestMethod();
|
|
||||||
this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true);
|
|
||||||
|
|
||||||
if (isHttps()) {
|
|
||||||
HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
|
|
||||||
cipherSuite = httpsConnection.getCipherSuite();
|
|
||||||
Certificate[] peerCertificatesNonFinal = null;
|
|
||||||
try {
|
|
||||||
peerCertificatesNonFinal = httpsConnection.getServerCertificates();
|
|
||||||
} catch (SSLPeerUnverifiedException ignored) {
|
|
||||||
}
|
|
||||||
peerCertificates = peerCertificatesNonFinal;
|
|
||||||
localCertificates = httpsConnection.getLocalCertificates();
|
|
||||||
} else {
|
|
||||||
cipherSuite = null;
|
|
||||||
peerCertificates = null;
|
|
||||||
localCertificates = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeTo(DiskLruCache.Editor editor) throws IOException {
|
|
||||||
OutputStream out = editor.newOutputStream(ENTRY_METADATA);
|
|
||||||
Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8));
|
|
||||||
|
|
||||||
writer.write(uri + '\n');
|
|
||||||
writer.write(requestMethod + '\n');
|
|
||||||
writer.write(Integer.toString(varyHeaders.length()) + '\n');
|
|
||||||
for (int i = 0; i < varyHeaders.length(); i++) {
|
|
||||||
writer.write(varyHeaders.getFieldName(i) + ": " + varyHeaders.getValue(i) + '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.write(responseHeaders.getStatusLine() + '\n');
|
|
||||||
writer.write(Integer.toString(responseHeaders.length()) + '\n');
|
|
||||||
for (int i = 0; i < responseHeaders.length(); i++) {
|
|
||||||
writer.write(responseHeaders.getFieldName(i) + ": " + responseHeaders.getValue(i) + '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isHttps()) {
|
|
||||||
writer.write('\n');
|
|
||||||
writer.write(cipherSuite + '\n');
|
|
||||||
writeCertArray(writer, peerCertificates);
|
|
||||||
writeCertArray(writer, localCertificates);
|
|
||||||
}
|
|
||||||
writer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isHttps() {
|
|
||||||
return uri.startsWith("https://");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Certificate[] readCertArray(StrictLineReader reader) throws IOException {
|
|
||||||
int length = reader.readInt();
|
|
||||||
if (length == -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
|
||||||
Certificate[] result = new Certificate[length];
|
|
||||||
for (int i = 0; i < result.length; i++) {
|
|
||||||
String line = reader.readLine();
|
|
||||||
byte[] bytes = Base64.decode(line.getBytes("US-ASCII"));
|
|
||||||
result[i] = certificateFactory.generateCertificate(new ByteArrayInputStream(bytes));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (CertificateException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException {
|
|
||||||
if (certificates == null) {
|
|
||||||
writer.write("-1\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
writer.write(Integer.toString(certificates.length) + '\n');
|
|
||||||
for (Certificate certificate : certificates) {
|
|
||||||
byte[] bytes = certificate.getEncoded();
|
|
||||||
String line = Base64.encode(bytes);
|
|
||||||
writer.write(line + '\n');
|
|
||||||
}
|
|
||||||
} catch (CertificateEncodingException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matches(URI uri, String requestMethod,
|
|
||||||
Map<String, List<String>> requestHeaders) {
|
|
||||||
return this.uri.equals(uri.toString())
|
|
||||||
&& this.requestMethod.equals(requestMethod)
|
|
||||||
&& new ResponseHeaders(uri, responseHeaders).varyMatches(varyHeaders.toMultimap(false),
|
|
||||||
requestHeaders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an input stream that reads the body of a snapshot, closing the
|
|
||||||
* snapshot when the stream is closed.
|
|
||||||
*/
|
|
||||||
private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) {
|
|
||||||
return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) {
|
|
||||||
@Override public void close() throws IOException {
|
|
||||||
snapshot.close();
|
|
||||||
super.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static class EntryCacheResponse extends CacheResponse {
|
|
||||||
private final Entry entry;
|
|
||||||
private final DiskLruCache.Snapshot snapshot;
|
|
||||||
private final InputStream in;
|
|
||||||
|
|
||||||
public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
|
|
||||||
this.entry = entry;
|
|
||||||
this.snapshot = snapshot;
|
|
||||||
this.in = newBodyInputStream(snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Map<String, List<String>> getHeaders() {
|
|
||||||
return entry.responseHeaders.toMultimap(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public InputStream getBody() {
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class EntrySecureCacheResponse extends SecureCacheResponse {
|
|
||||||
private final Entry entry;
|
|
||||||
private final DiskLruCache.Snapshot snapshot;
|
|
||||||
private final InputStream in;
|
|
||||||
|
|
||||||
public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
|
|
||||||
this.entry = entry;
|
|
||||||
this.snapshot = snapshot;
|
|
||||||
this.in = newBodyInputStream(snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Map<String, List<String>> getHeaders() {
|
|
||||||
return entry.responseHeaders.toMultimap(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public InputStream getBody() {
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public String getCipherSuite() {
|
|
||||||
return entry.cipherSuite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public List<Certificate> getServerCertificateChain()
|
|
||||||
throws SSLPeerUnverifiedException {
|
|
||||||
if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
|
|
||||||
throw new SSLPeerUnverifiedException(null);
|
|
||||||
}
|
|
||||||
return Arrays.asList(entry.peerCertificates.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
|
|
||||||
if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
|
|
||||||
throw new SSLPeerUnverifiedException(null);
|
|
||||||
}
|
|
||||||
return ((X509Certificate) entry.peerCertificates[0]).getSubjectX500Principal();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public List<Certificate> getLocalCertificateChain() {
|
|
||||||
if (entry.localCertificates == null || entry.localCertificates.length == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Arrays.asList(entry.localCertificates.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Principal getLocalPrincipal() {
|
|
||||||
if (entry.localCertificates == null || entry.localCertificates.length == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ((X509Certificate) entry.localCertificates[0]).getSubjectX500Principal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
48
framework/src/com/squareup/okhttp/internal/http/HttpTransport.java
Normal file → Executable file
@@ -78,18 +78,23 @@ public final class HttpTransport implements Transport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stream a request body of a known length.
|
// Stream a request body of a known length.
|
||||||
int fixedContentLength = httpEngine.policy.getFixedContentLength();
|
long fixedContentLength = httpEngine.policy.getFixedContentLength();
|
||||||
if (fixedContentLength != -1) {
|
if (fixedContentLength != -1) {
|
||||||
httpEngine.requestHeaders.setContentLength(fixedContentLength);
|
httpEngine.requestHeaders.setContentLength(fixedContentLength);
|
||||||
writeRequestHeaders();
|
writeRequestHeaders();
|
||||||
return new FixedLengthOutputStream(requestOut, fixedContentLength);
|
return new FixedLengthOutputStream(requestOut, fixedContentLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long contentLength = httpEngine.requestHeaders.getContentLength();
|
||||||
|
if (contentLength > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("Use setFixedLengthStreamingMode() or "
|
||||||
|
+ "setChunkedStreamingMode() for requests larger than 2 GiB.");
|
||||||
|
}
|
||||||
|
|
||||||
// Buffer a request body of a known length.
|
// Buffer a request body of a known length.
|
||||||
int contentLength = httpEngine.requestHeaders.getContentLength();
|
|
||||||
if (contentLength != -1) {
|
if (contentLength != -1) {
|
||||||
writeRequestHeaders();
|
writeRequestHeaders();
|
||||||
return new RetryableOutputStream(contentLength);
|
return new RetryableOutputStream((int) contentLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffer a request body of an unknown length. Don't write request
|
// Buffer a request body of an unknown length. Don't write request
|
||||||
@@ -127,15 +132,18 @@ public final class HttpTransport implements Transport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override public ResponseHeaders readResponseHeaders() throws IOException {
|
@Override public ResponseHeaders readResponseHeaders() throws IOException {
|
||||||
RawHeaders headers = RawHeaders.fromBytes(socketIn);
|
RawHeaders rawHeaders = RawHeaders.fromBytes(socketIn);
|
||||||
httpEngine.connection.setHttpMinorVersion(headers.getHttpMinorVersion());
|
httpEngine.connection.setHttpMinorVersion(rawHeaders.getHttpMinorVersion());
|
||||||
httpEngine.receiveHeaders(headers);
|
httpEngine.receiveHeaders(rawHeaders);
|
||||||
return new ResponseHeaders(httpEngine.uri, headers);
|
|
||||||
|
ResponseHeaders headers = new ResponseHeaders(httpEngine.uri, rawHeaders);
|
||||||
|
headers.setTransport("http/1.1");
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut,
|
public boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
|
||||||
InputStream responseBodyIn) {
|
InputStream responseBodyIn) {
|
||||||
if (streamCancelled) {
|
if (streamCanceled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +177,10 @@ public final class HttpTransport implements Transport {
|
|||||||
* Discards the response body so that the connection can be reused. This
|
* Discards the response body so that the connection can be reused. This
|
||||||
* needs to be done judiciously, since it delays the current request in
|
* needs to be done judiciously, since it delays the current request in
|
||||||
* order to speed up a potential future request that may never occur.
|
* order to speed up a potential future request that may never occur.
|
||||||
|
*
|
||||||
|
* <p>A stream may be discarded to encourage response caching (a response
|
||||||
|
* cannot be cached unless it is consumed completely) or to enable connection
|
||||||
|
* reuse.
|
||||||
*/
|
*/
|
||||||
private static boolean discardStream(HttpEngine httpEngine, InputStream responseBodyIn) {
|
private static boolean discardStream(HttpEngine httpEngine, InputStream responseBodyIn) {
|
||||||
Connection connection = httpEngine.connection;
|
Connection connection = httpEngine.connection;
|
||||||
@@ -212,9 +224,9 @@ public final class HttpTransport implements Transport {
|
|||||||
/** An HTTP body with a fixed length known in advance. */
|
/** An HTTP body with a fixed length known in advance. */
|
||||||
private static final class FixedLengthOutputStream extends AbstractOutputStream {
|
private static final class FixedLengthOutputStream extends AbstractOutputStream {
|
||||||
private final OutputStream socketOut;
|
private final OutputStream socketOut;
|
||||||
private int bytesRemaining;
|
private long bytesRemaining;
|
||||||
|
|
||||||
private FixedLengthOutputStream(OutputStream socketOut, int bytesRemaining) {
|
private FixedLengthOutputStream(OutputStream socketOut, long bytesRemaining) {
|
||||||
this.socketOut = socketOut;
|
this.socketOut = socketOut;
|
||||||
this.bytesRemaining = bytesRemaining;
|
this.bytesRemaining = bytesRemaining;
|
||||||
}
|
}
|
||||||
@@ -358,14 +370,14 @@ public final class HttpTransport implements Transport {
|
|||||||
|
|
||||||
/** An HTTP body with a fixed length specified in advance. */
|
/** An HTTP body with a fixed length specified in advance. */
|
||||||
private static class FixedLengthInputStream extends AbstractHttpInputStream {
|
private static class FixedLengthInputStream extends AbstractHttpInputStream {
|
||||||
private int bytesRemaining;
|
private long bytesRemaining;
|
||||||
|
|
||||||
public FixedLengthInputStream(InputStream is, CacheRequest cacheRequest, HttpEngine httpEngine,
|
public FixedLengthInputStream(InputStream is, CacheRequest cacheRequest, HttpEngine httpEngine,
|
||||||
int length) throws IOException {
|
long length) throws IOException {
|
||||||
super(is, httpEngine, cacheRequest);
|
super(is, httpEngine, cacheRequest);
|
||||||
bytesRemaining = length;
|
bytesRemaining = length;
|
||||||
if (bytesRemaining == 0) {
|
if (bytesRemaining == 0) {
|
||||||
endOfInput(false);
|
endOfInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +387,7 @@ public final class HttpTransport implements Transport {
|
|||||||
if (bytesRemaining == 0) {
|
if (bytesRemaining == 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int read = in.read(buffer, offset, Math.min(count, bytesRemaining));
|
int read = in.read(buffer, offset, (int) Math.min(count, bytesRemaining));
|
||||||
if (read == -1) {
|
if (read == -1) {
|
||||||
unexpectedEndOfInput(); // the server didn't supply the promised content length
|
unexpectedEndOfInput(); // the server didn't supply the promised content length
|
||||||
throw new ProtocolException("unexpected end of stream");
|
throw new ProtocolException("unexpected end of stream");
|
||||||
@@ -383,14 +395,14 @@ public final class HttpTransport implements Transport {
|
|||||||
bytesRemaining -= read;
|
bytesRemaining -= read;
|
||||||
cacheWrite(buffer, offset, read);
|
cacheWrite(buffer, offset, read);
|
||||||
if (bytesRemaining == 0) {
|
if (bytesRemaining == 0) {
|
||||||
endOfInput(false);
|
endOfInput();
|
||||||
}
|
}
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int available() throws IOException {
|
@Override public int available() throws IOException {
|
||||||
checkNotClosed();
|
checkNotClosed();
|
||||||
return bytesRemaining == 0 ? 0 : Math.min(in.available(), bytesRemaining);
|
return bytesRemaining == 0 ? 0 : (int) Math.min(in.available(), bytesRemaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void close() throws IOException {
|
@Override public void close() throws IOException {
|
||||||
@@ -460,7 +472,7 @@ public final class HttpTransport implements Transport {
|
|||||||
RawHeaders rawResponseHeaders = httpEngine.responseHeaders.getHeaders();
|
RawHeaders rawResponseHeaders = httpEngine.responseHeaders.getHeaders();
|
||||||
RawHeaders.readHeaders(transport.socketIn, rawResponseHeaders);
|
RawHeaders.readHeaders(transport.socketIn, rawResponseHeaders);
|
||||||
httpEngine.receiveHeaders(rawResponseHeaders);
|
httpEngine.receiveHeaders(rawResponseHeaders);
|
||||||
endOfInput(false);
|
endOfInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
226
framework/src/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
Normal file → Executable file
@@ -18,33 +18,28 @@
|
|||||||
package com.squareup.okhttp.internal.http;
|
package com.squareup.okhttp.internal.http;
|
||||||
|
|
||||||
import com.squareup.okhttp.Connection;
|
import com.squareup.okhttp.Connection;
|
||||||
import com.squareup.okhttp.ConnectionPool;
|
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
import com.squareup.okhttp.OkHttpClient;
|
||||||
import com.squareup.okhttp.Route;
|
import com.squareup.okhttp.internal.Platform;
|
||||||
import com.squareup.okhttp.internal.AbstractOutputStream;
|
|
||||||
import com.squareup.okhttp.internal.FaultRecoveringOutputStream;
|
|
||||||
import com.squareup.okhttp.internal.Util;
|
import com.squareup.okhttp.internal.Util;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.CookieHandler;
|
|
||||||
import java.net.HttpRetryException;
|
import java.net.HttpRetryException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.ProxySelector;
|
|
||||||
import java.net.SocketPermission;
|
import java.net.SocketPermission;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.Permission;
|
import java.security.Permission;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.net.ssl.HostnameVerifier;
|
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
|
|
||||||
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
||||||
|
|
||||||
@@ -62,10 +57,10 @@ import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
|||||||
* connection} field on this class for null/non-null to determine of an instance
|
* connection} field on this class for null/non-null to determine of an instance
|
||||||
* is currently connected to a server.
|
* is currently connected to a server.
|
||||||
*/
|
*/
|
||||||
public class HttpURLConnectionImpl extends HttpURLConnection {
|
public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||||
|
|
||||||
/** Numeric status code, 307: Temporary Redirect. */
|
/** Numeric status code, 307: Temporary Redirect. */
|
||||||
static final int HTTP_TEMP_REDIRECT = 307;
|
public static final int HTTP_TEMP_REDIRECT = 307;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How many redirects should we follow? Chrome follows 21; Firefox, curl,
|
* How many redirects should we follow? Chrome follows 21; Firefox, curl,
|
||||||
@@ -73,51 +68,19 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
*/
|
*/
|
||||||
private static final int MAX_REDIRECTS = 20;
|
private static final int MAX_REDIRECTS = 20;
|
||||||
|
|
||||||
/**
|
final OkHttpClient client;
|
||||||
* The minimum number of request body bytes to transmit before we're willing
|
|
||||||
* to let a routine {@link IOException} bubble up to the user. This is used to
|
|
||||||
* size a buffer for data that will be replayed upon error.
|
|
||||||
*/
|
|
||||||
private static final int MAX_REPLAY_BUFFER_LENGTH = 8192;
|
|
||||||
|
|
||||||
private final boolean followProtocolRedirects;
|
|
||||||
|
|
||||||
/** The proxy requested by the client, or null for a proxy to be selected automatically. */
|
|
||||||
final Proxy requestedProxy;
|
|
||||||
|
|
||||||
final ProxySelector proxySelector;
|
|
||||||
final CookieHandler cookieHandler;
|
|
||||||
final OkResponseCache responseCache;
|
|
||||||
final ConnectionPool connectionPool;
|
|
||||||
/* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */
|
|
||||||
SSLSocketFactory sslSocketFactory;
|
|
||||||
HostnameVerifier hostnameVerifier;
|
|
||||||
final Set<Route> failedRoutes;
|
|
||||||
|
|
||||||
private final RawHeaders rawRequestHeaders = new RawHeaders();
|
private final RawHeaders rawRequestHeaders = new RawHeaders();
|
||||||
|
/** Like the superclass field of the same name, but a long and available on all platforms. */
|
||||||
|
private long fixedContentLength = -1;
|
||||||
private int redirectionCount;
|
private int redirectionCount;
|
||||||
private FaultRecoveringOutputStream faultRecoveringRequestBody;
|
|
||||||
|
|
||||||
protected IOException httpEngineFailure;
|
protected IOException httpEngineFailure;
|
||||||
protected HttpEngine httpEngine;
|
protected HttpEngine httpEngine;
|
||||||
|
private Proxy selectedProxy;
|
||||||
|
|
||||||
public HttpURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache,
|
public HttpURLConnectionImpl(URL url, OkHttpClient client) {
|
||||||
Set<Route> failedRoutes) {
|
|
||||||
super(url);
|
super(url);
|
||||||
this.followProtocolRedirects = client.getFollowProtocolRedirects();
|
this.client = client;
|
||||||
this.failedRoutes = failedRoutes;
|
|
||||||
this.requestedProxy = client.getProxy();
|
|
||||||
this.proxySelector = client.getProxySelector();
|
|
||||||
this.cookieHandler = client.getCookieHandler();
|
|
||||||
this.connectionPool = client.getConnectionPool();
|
|
||||||
this.sslSocketFactory = client.getSslSocketFactory();
|
|
||||||
this.hostnameVerifier = client.getHostnameVerifier();
|
|
||||||
this.responseCache = responseCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Route> getFailedRoutes() {
|
|
||||||
return failedRoutes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public final void connect() throws IOException {
|
@Override public final void connect() throws IOException {
|
||||||
@@ -197,7 +160,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
try {
|
try {
|
||||||
return getResponse().getResponseHeaders().getHeaders().toMultimap(true);
|
return getResponse().getResponseHeaders().getHeaders().toMultimap(true);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return null;
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,29 +204,14 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
throw new ProtocolException("cannot write request body after response has been read");
|
throw new ProtocolException("cannot write request body after response has been read");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (faultRecoveringRequestBody == null) {
|
return out;
|
||||||
faultRecoveringRequestBody = new FaultRecoveringOutputStream(MAX_REPLAY_BUFFER_LENGTH, out) {
|
|
||||||
@Override protected OutputStream replacementStream(IOException e) throws IOException {
|
|
||||||
if (httpEngine.getRequestBody() instanceof AbstractOutputStream
|
|
||||||
&& ((AbstractOutputStream) httpEngine.getRequestBody()).isClosed()) {
|
|
||||||
return null; // Don't recover once the underlying stream has been closed.
|
|
||||||
}
|
|
||||||
if (handleFailure(e)) {
|
|
||||||
return httpEngine.getRequestBody();
|
|
||||||
}
|
|
||||||
return null; // This is a permanent failure.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return faultRecoveringRequestBody;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public final Permission getPermission() throws IOException {
|
@Override public final Permission getPermission() throws IOException {
|
||||||
String hostName = getURL().getHost();
|
String hostName = getURL().getHost();
|
||||||
int hostPort = Util.getEffectivePort(getURL());
|
int hostPort = Util.getEffectivePort(getURL());
|
||||||
if (usingProxy()) {
|
if (usingProxy()) {
|
||||||
InetSocketAddress proxyAddress = (InetSocketAddress) requestedProxy.address();
|
InetSocketAddress proxyAddress = (InetSocketAddress) client.getProxy().address();
|
||||||
hostName = proxyAddress.getHostName();
|
hostName = proxyAddress.getHostName();
|
||||||
hostPort = proxyAddress.getPort();
|
hostPort = proxyAddress.getPort();
|
||||||
}
|
}
|
||||||
@@ -277,6 +225,22 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
return rawRequestHeaders.get(field);
|
return rawRequestHeaders.get(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public void setConnectTimeout(int timeoutMillis) {
|
||||||
|
client.setConnectTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int getConnectTimeout() {
|
||||||
|
return client.getConnectTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void setReadTimeout(int timeoutMillis) {
|
||||||
|
client.setReadTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int getReadTimeout() {
|
||||||
|
return client.getReadTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
private void initHttpEngine() throws IOException {
|
private void initHttpEngine() throws IOException {
|
||||||
if (httpEngineFailure != null) {
|
if (httpEngineFailure != null) {
|
||||||
throw httpEngineFailure;
|
throw httpEngineFailure;
|
||||||
@@ -290,8 +254,8 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
if (method.equals("GET")) {
|
if (method.equals("GET")) {
|
||||||
// they are requesting a stream to write to. This implies a POST method
|
// they are requesting a stream to write to. This implies a POST method
|
||||||
method = "POST";
|
method = "POST";
|
||||||
} else if (!method.equals("POST") && !method.equals("PUT")) {
|
} else if (!method.equals("POST") && !method.equals("PUT") && !method.equals("PATCH")) {
|
||||||
// If the request method is neither POST nor PUT, then you're not writing
|
// If the request method is neither POST nor PUT nor PATCH, then you're not writing
|
||||||
throw new ProtocolException(method + " does not support writing");
|
throw new ProtocolException(method + " does not support writing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,17 +266,16 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpURLConnection getHttpConnectionToCache() {
|
@Override public HttpURLConnection getHttpConnectionToCache() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
|
private HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
|
||||||
Connection connection, RetryableOutputStream requestBody) throws IOException {
|
Connection connection, RetryableOutputStream requestBody) throws IOException {
|
||||||
if (url.getProtocol().equals("http")) {
|
if (url.getProtocol().equals("http")) {
|
||||||
return new HttpEngine(this, method, requestHeaders, connection, requestBody);
|
return new HttpEngine(client, this, method, requestHeaders, connection, requestBody);
|
||||||
} else if (url.getProtocol().equals("https")) {
|
} else if (url.getProtocol().equals("https")) {
|
||||||
return new HttpsURLConnectionImpl.HttpsEngine(
|
return new HttpsEngine(client, this, method, requestHeaders, connection, requestBody);
|
||||||
this, method, requestHeaders, connection, requestBody);
|
|
||||||
} else {
|
} else {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
@@ -348,7 +311,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
// Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM
|
// Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM
|
||||||
// redirect should keep the same method, Chrome, Firefox and the
|
// redirect should keep the same method, Chrome, Firefox and the
|
||||||
// RI all issue GETs when following any redirect.
|
// RI all issue GETs when following any redirect.
|
||||||
int responseCode = getResponseCode();
|
int responseCode = httpEngine.getResponseCode();
|
||||||
if (responseCode == HTTP_MULT_CHOICE
|
if (responseCode == HTTP_MULT_CHOICE
|
||||||
|| responseCode == HTTP_MOVED_PERM
|
|| responseCode == HTTP_MOVED_PERM
|
||||||
|| responseCode == HTTP_MOVED_TEMP
|
|| responseCode == HTTP_MOVED_TEMP
|
||||||
@@ -358,8 +321,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
|
if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
|
||||||
throw new HttpRetryException("Cannot retry streamed HTTP body",
|
throw new HttpRetryException("Cannot retry streamed HTTP body", responseCode);
|
||||||
httpEngine.getResponseCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retry == Retry.DIFFERENT_CONNECTION) {
|
if (retry == Retry.DIFFERENT_CONNECTION) {
|
||||||
@@ -370,6 +332,11 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
|
|
||||||
httpEngine = newHttpEngine(retryMethod, rawRequestHeaders, httpEngine.getConnection(),
|
httpEngine = newHttpEngine(retryMethod, rawRequestHeaders, httpEngine.getConnection(),
|
||||||
(RetryableOutputStream) requestBody);
|
(RetryableOutputStream) requestBody);
|
||||||
|
|
||||||
|
if (requestBody == null) {
|
||||||
|
// Drop the Content-Length header when redirected from POST to GET.
|
||||||
|
httpEngine.getRequestHeaders().removeContentLength();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,6 +351,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
if (readResponse) {
|
if (readResponse) {
|
||||||
httpEngine.readResponse();
|
httpEngine.readResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (handleFailure(e)) {
|
if (handleFailure(e)) {
|
||||||
@@ -407,8 +375,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
|
|
||||||
OutputStream requestBody = httpEngine.getRequestBody();
|
OutputStream requestBody = httpEngine.getRequestBody();
|
||||||
boolean canRetryRequestBody = requestBody == null
|
boolean canRetryRequestBody = requestBody == null
|
||||||
|| requestBody instanceof RetryableOutputStream
|
|| requestBody instanceof RetryableOutputStream;
|
||||||
|| (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable());
|
|
||||||
if (routeSelector == null && httpEngine.connection == null // No connection.
|
if (routeSelector == null && httpEngine.connection == null // No connection.
|
||||||
|| routeSelector != null && !routeSelector.hasNext() // No more routes to attempt.
|
|| routeSelector != null && !routeSelector.hasNext() // No more routes to attempt.
|
||||||
|| !isRecoverable(e)
|
|| !isRecoverable(e)
|
||||||
@@ -418,15 +385,9 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
httpEngine.release(true);
|
httpEngine.release(true);
|
||||||
RetryableOutputStream retryableOutputStream = requestBody instanceof RetryableOutputStream
|
RetryableOutputStream retryableOutputStream = (RetryableOutputStream) requestBody;
|
||||||
? (RetryableOutputStream) requestBody
|
|
||||||
: null;
|
|
||||||
httpEngine = newHttpEngine(method, rawRequestHeaders, null, retryableOutputStream);
|
httpEngine = newHttpEngine(method, rawRequestHeaders, null, retryableOutputStream);
|
||||||
httpEngine.routeSelector = routeSelector; // Keep the same routeSelector.
|
httpEngine.routeSelector = routeSelector; // Keep the same routeSelector.
|
||||||
if (faultRecoveringRequestBody != null && faultRecoveringRequestBody.isRecoverable()) {
|
|
||||||
httpEngine.sendRequest();
|
|
||||||
faultRecoveringRequestBody.replaceStream(httpEngine.getRequestBody());
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,13 +412,13 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the retry action to take for the current response headers. The
|
* Returns the retry action to take for the current response headers. The
|
||||||
* headers, proxy and target URL or this connection may be adjusted to
|
* headers, proxy and target URL for this connection may be adjusted to
|
||||||
* prepare for a follow up request.
|
* prepare for a follow up request.
|
||||||
*/
|
*/
|
||||||
private Retry processResponseHeaders() throws IOException {
|
private Retry processResponseHeaders() throws IOException {
|
||||||
Proxy selectedProxy = httpEngine.connection != null
|
Proxy selectedProxy = httpEngine.connection != null
|
||||||
? httpEngine.connection.getRoute().getProxy()
|
? httpEngine.connection.getRoute().getProxy()
|
||||||
: requestedProxy;
|
: client.getProxy();
|
||||||
final int responseCode = getResponseCode();
|
final int responseCode = getResponseCode();
|
||||||
switch (responseCode) {
|
switch (responseCode) {
|
||||||
case HTTP_PROXY_AUTH:
|
case HTTP_PROXY_AUTH:
|
||||||
@@ -466,8 +427,9 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
}
|
}
|
||||||
// fall-through
|
// fall-through
|
||||||
case HTTP_UNAUTHORIZED:
|
case HTTP_UNAUTHORIZED:
|
||||||
boolean credentialsFound = HttpAuthenticator.processAuthHeader(getResponseCode(),
|
boolean credentialsFound = HttpAuthenticator.processAuthHeader(client.getAuthenticator(),
|
||||||
httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders, selectedProxy, url);
|
getResponseCode(), httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders,
|
||||||
|
selectedProxy, url);
|
||||||
return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE;
|
return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE;
|
||||||
|
|
||||||
case HTTP_MULT_CHOICE:
|
case HTTP_MULT_CHOICE:
|
||||||
@@ -496,7 +458,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
return Retry.NONE; // Don't follow redirects to unsupported protocols.
|
return Retry.NONE; // Don't follow redirects to unsupported protocols.
|
||||||
}
|
}
|
||||||
boolean sameProtocol = previousUrl.getProtocol().equals(url.getProtocol());
|
boolean sameProtocol = previousUrl.getProtocol().equals(url.getProtocol());
|
||||||
if (!sameProtocol && !followProtocolRedirects) {
|
if (!sameProtocol && !client.getFollowProtocolRedirects()) {
|
||||||
return Retry.NONE; // This client doesn't follow redirects across protocols.
|
return Retry.NONE; // This client doesn't follow redirects across protocols.
|
||||||
}
|
}
|
||||||
boolean sameHost = previousUrl.getHost().equals(url.getHost());
|
boolean sameHost = previousUrl.getHost().equals(url.getHost());
|
||||||
@@ -513,17 +475,29 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @see java.net.HttpURLConnection#setFixedLengthStreamingMode(int) */
|
/** @see java.net.HttpURLConnection#setFixedLengthStreamingMode(int) */
|
||||||
final int getFixedContentLength() {
|
@Override public final long getFixedContentLength() {
|
||||||
return fixedContentLength;
|
return fixedContentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see java.net.HttpURLConnection#setChunkedStreamingMode(int) */
|
@Override public final int getChunkLength() {
|
||||||
final int getChunkLength() {
|
|
||||||
return chunkLength;
|
return chunkLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public final boolean usingProxy() {
|
@Override public final boolean usingProxy() {
|
||||||
return (requestedProxy != null && requestedProxy.type() != Proxy.Type.DIRECT);
|
if (selectedProxy != null) {
|
||||||
|
return isValidNonDirectProxy(selectedProxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This behavior is a bit odd (but is probably justified by the
|
||||||
|
// oddness of the APIs involved). Before a connection is established,
|
||||||
|
// this method will return true only if this connection was explicitly
|
||||||
|
// opened with a Proxy. We don't attempt to query the ProxySelector
|
||||||
|
// at all.
|
||||||
|
return isValidNonDirectProxy(client.getProxy());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidNonDirectProxy(Proxy proxy) {
|
||||||
|
return proxy != null && proxy.type() != Proxy.Type.DIRECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String getResponseMessage() throws IOException {
|
@Override public String getResponseMessage() throws IOException {
|
||||||
@@ -541,7 +515,21 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
if (field == null) {
|
if (field == null) {
|
||||||
throw new NullPointerException("field == null");
|
throw new NullPointerException("field == null");
|
||||||
}
|
}
|
||||||
rawRequestHeaders.set(field, newValue);
|
if (newValue == null) {
|
||||||
|
// Silently ignore null header values for backwards compatibility with older
|
||||||
|
// android versions as well as with other URLConnection implementations.
|
||||||
|
//
|
||||||
|
// Some implementations send a malformed HTTP header when faced with
|
||||||
|
// such requests, we respect the spec and ignore the header.
|
||||||
|
Platform.get().logW("Ignoring header " + field + " because its value was null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("X-Android-Transports".equals(field)) {
|
||||||
|
setTransports(newValue, false /* append */);
|
||||||
|
} else {
|
||||||
|
rawRequestHeaders.set(field, newValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public final void addRequestProperty(String field, String value) {
|
@Override public final void addRequestProperty(String field, String value) {
|
||||||
@@ -551,6 +539,52 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
|||||||
if (field == null) {
|
if (field == null) {
|
||||||
throw new NullPointerException("field == null");
|
throw new NullPointerException("field == null");
|
||||||
}
|
}
|
||||||
rawRequestHeaders.add(field, value);
|
if (value == null) {
|
||||||
|
// Silently ignore null header values for backwards compatibility with older
|
||||||
|
// android versions as well as with other URLConnection implementations.
|
||||||
|
//
|
||||||
|
// Some implementations send a malformed HTTP header when faced with
|
||||||
|
// such requests, we respect the spec and ignore the header.
|
||||||
|
Platform.get().logW("Ignoring header " + field + " because its value was null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("X-Android-Transports".equals(field)) {
|
||||||
|
setTransports(value, true /* append */);
|
||||||
|
} else {
|
||||||
|
rawRequestHeaders.add(field, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Splits and validates a comma-separated string of transports.
|
||||||
|
* When append == false, we require that the transport list contains "http/1.1".
|
||||||
|
*/
|
||||||
|
private void setTransports(String transportsString, boolean append) {
|
||||||
|
List<String> transportsList = new ArrayList<String>();
|
||||||
|
if (append) {
|
||||||
|
transportsList.addAll(client.getTransports());
|
||||||
|
}
|
||||||
|
for (String transport : transportsString.split(",", -1)) {
|
||||||
|
transportsList.add(transport);
|
||||||
|
}
|
||||||
|
client.setTransports(transportsList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void setFixedLengthStreamingMode(int contentLength) {
|
||||||
|
setFixedLengthStreamingMode((long) contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override Don't override: this overload method doesn't exist prior to Java 1.7.
|
||||||
|
public void setFixedLengthStreamingMode(long contentLength) {
|
||||||
|
if (super.connected) throw new IllegalStateException("Already connected");
|
||||||
|
if (chunkLength > 0) throw new IllegalStateException("Already in chunked mode");
|
||||||
|
if (contentLength < 0) throw new IllegalArgumentException("contentLength < 0");
|
||||||
|
this.fixedContentLength = contentLength;
|
||||||
|
super.fixedContentLength = (int) Math.min(contentLength, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public final void setSelectedProxy(Proxy proxy) {
|
||||||
|
this.selectedProxy = proxy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
framework/src/com/squareup/okhttp/internal/http/HttpsEngine.java
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You 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 com.squareup.okhttp.internal.http;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.Connection;
|
||||||
|
import com.squareup.okhttp.OkHttpClient;
|
||||||
|
import com.squareup.okhttp.TunnelRequest;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.CacheResponse;
|
||||||
|
import java.net.SecureCacheResponse;
|
||||||
|
import java.net.URL;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
||||||
|
|
||||||
|
public final class HttpsEngine extends HttpEngine {
|
||||||
|
/**
|
||||||
|
* Stash of HttpsEngine.connection.socket to implement requests like {@code
|
||||||
|
* HttpsURLConnection#getCipherSuite} even after the connection has been
|
||||||
|
* recycled.
|
||||||
|
*/
|
||||||
|
private SSLSocket sslSocket;
|
||||||
|
|
||||||
|
public HttpsEngine(OkHttpClient client, Policy policy, String method, RawHeaders requestHeaders,
|
||||||
|
Connection connection, RetryableOutputStream requestBody) throws IOException {
|
||||||
|
super(client, policy, method, requestHeaders, connection, requestBody);
|
||||||
|
this.sslSocket = connection != null ? (SSLSocket) connection.getSocket() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void connected(Connection connection) {
|
||||||
|
this.sslSocket = (SSLSocket) connection.getSocket();
|
||||||
|
super.connected(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
|
||||||
|
return cacheResponse instanceof SecureCacheResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected boolean includeAuthorityInRequestLine() {
|
||||||
|
// Even if there is a proxy, it isn't involved. Always request just the path.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SSLSocket getSslSocket() {
|
||||||
|
return sslSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected TunnelRequest getTunnelConfig() {
|
||||||
|
String userAgent = requestHeaders.getUserAgent();
|
||||||
|
if (userAgent == null) {
|
||||||
|
userAgent = getDefaultUserAgent();
|
||||||
|
}
|
||||||
|
|
||||||
|
URL url = policy.getURL();
|
||||||
|
return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent,
|
||||||
|
requestHeaders.getProxyAuthorization());
|
||||||
|
}
|
||||||
|
}
|
||||||
231
framework/src/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
Normal file → Executable file
@@ -16,14 +16,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.okhttp.internal.http;
|
package com.squareup.okhttp.internal.http;
|
||||||
|
|
||||||
import com.squareup.okhttp.Connection;
|
import android.annotation.SuppressLint;
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
import com.squareup.okhttp.OkHttpClient;
|
||||||
import com.squareup.okhttp.Route;
|
|
||||||
import com.squareup.okhttp.TunnelRequest;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.CacheResponse;
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
import java.net.SecureCacheResponse;
|
import java.net.SecureCacheResponse;
|
||||||
@@ -33,24 +30,20 @@ import java.security.Principal;
|
|||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
|
||||||
|
|
||||||
public final class HttpsURLConnectionImpl extends HttpsURLConnection {
|
public final class HttpsURLConnectionImpl extends HttpsURLConnection {
|
||||||
|
|
||||||
/** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */
|
/** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */
|
||||||
private final HttpUrlConnectionDelegate delegate;
|
private final HttpUrlConnectionDelegate delegate;
|
||||||
|
|
||||||
public HttpsURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache,
|
public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
|
||||||
Set<Route> failedRoutes) {
|
|
||||||
super(url);
|
super(url);
|
||||||
delegate = new HttpUrlConnectionDelegate(url, client, responseCache, failedRoutes);
|
delegate = new HttpUrlConnectionDelegate(url, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String getCipherSuite() {
|
@Override public String getCipherSuite() {
|
||||||
@@ -120,294 +113,247 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SSLSocket getSslSocket() {
|
private SSLSocket getSslSocket() {
|
||||||
if (delegate.httpEngine == null || delegate.httpEngine.sentRequestMillis == -1) {
|
if (delegate.httpEngine == null || !delegate.httpEngine.connected) {
|
||||||
throw new IllegalStateException("Connection has not yet been established");
|
throw new IllegalStateException("Connection has not yet been established");
|
||||||
}
|
}
|
||||||
return delegate.httpEngine instanceof HttpsEngine
|
return delegate.httpEngine instanceof HttpsEngine
|
||||||
? ((HttpsEngine) delegate.httpEngine).sslSocket
|
? ((HttpsEngine) delegate.httpEngine).getSslSocket()
|
||||||
: null; // Not HTTPS! Probably an https:// to http:// redirect.
|
: null; // Not HTTPS! Probably an https:// to http:// redirect.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void disconnect() {
|
||||||
public void disconnect() {
|
|
||||||
delegate.disconnect();
|
delegate.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public InputStream getErrorStream() {
|
||||||
public InputStream getErrorStream() {
|
|
||||||
return delegate.getErrorStream();
|
return delegate.getErrorStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String getRequestMethod() {
|
||||||
public String getRequestMethod() {
|
|
||||||
return delegate.getRequestMethod();
|
return delegate.getRequestMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public int getResponseCode() throws IOException {
|
||||||
public int getResponseCode() throws IOException {
|
|
||||||
return delegate.getResponseCode();
|
return delegate.getResponseCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String getResponseMessage() throws IOException {
|
||||||
public String getResponseMessage() throws IOException {
|
|
||||||
return delegate.getResponseMessage();
|
return delegate.getResponseMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setRequestMethod(String method) throws ProtocolException {
|
||||||
public void setRequestMethod(String method) throws ProtocolException {
|
|
||||||
delegate.setRequestMethod(method);
|
delegate.setRequestMethod(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public boolean usingProxy() {
|
||||||
public boolean usingProxy() {
|
|
||||||
return delegate.usingProxy();
|
return delegate.usingProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public boolean getInstanceFollowRedirects() {
|
||||||
public boolean getInstanceFollowRedirects() {
|
|
||||||
return delegate.getInstanceFollowRedirects();
|
return delegate.getInstanceFollowRedirects();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setInstanceFollowRedirects(boolean followRedirects) {
|
||||||
public void setInstanceFollowRedirects(boolean followRedirects) {
|
|
||||||
delegate.setInstanceFollowRedirects(followRedirects);
|
delegate.setInstanceFollowRedirects(followRedirects);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void connect() throws IOException {
|
||||||
public void connect() throws IOException {
|
|
||||||
connected = true;
|
connected = true;
|
||||||
delegate.connect();
|
delegate.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public boolean getAllowUserInteraction() {
|
||||||
public boolean getAllowUserInteraction() {
|
|
||||||
return delegate.getAllowUserInteraction();
|
return delegate.getAllowUserInteraction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public Object getContent() throws IOException {
|
||||||
public Object getContent() throws IOException {
|
|
||||||
return delegate.getContent();
|
return delegate.getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") // Spec does not generify
|
@SuppressWarnings("unchecked") // Spec does not generify
|
||||||
@Override
|
@Override public Object getContent(Class[] types) throws IOException {
|
||||||
public Object getContent(Class[] types) throws IOException {
|
|
||||||
return delegate.getContent(types);
|
return delegate.getContent(types);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String getContentEncoding() {
|
||||||
public String getContentEncoding() {
|
|
||||||
return delegate.getContentEncoding();
|
return delegate.getContentEncoding();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public int getContentLength() {
|
||||||
public int getContentLength() {
|
|
||||||
return delegate.getContentLength();
|
return delegate.getContentLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String getContentType() {
|
||||||
public String getContentType() {
|
|
||||||
return delegate.getContentType();
|
return delegate.getContentType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public long getDate() {
|
||||||
public long getDate() {
|
|
||||||
return delegate.getDate();
|
return delegate.getDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public boolean getDefaultUseCaches() {
|
||||||
public boolean getDefaultUseCaches() {
|
|
||||||
return delegate.getDefaultUseCaches();
|
return delegate.getDefaultUseCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public boolean getDoInput() {
|
||||||
public boolean getDoInput() {
|
|
||||||
return delegate.getDoInput();
|
return delegate.getDoInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public boolean getDoOutput() {
|
||||||
public boolean getDoOutput() {
|
|
||||||
return delegate.getDoOutput();
|
return delegate.getDoOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public long getExpiration() {
|
||||||
public long getExpiration() {
|
|
||||||
return delegate.getExpiration();
|
return delegate.getExpiration();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String getHeaderField(int pos) {
|
||||||
public String getHeaderField(int pos) {
|
|
||||||
return delegate.getHeaderField(pos);
|
return delegate.getHeaderField(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public Map<String, List<String>> getHeaderFields() {
|
||||||
public Map<String, List<String>> getHeaderFields() {
|
|
||||||
return delegate.getHeaderFields();
|
return delegate.getHeaderFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public Map<String, List<String>> getRequestProperties() {
|
||||||
public Map<String, List<String>> getRequestProperties() {
|
|
||||||
return delegate.getRequestProperties();
|
return delegate.getRequestProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void addRequestProperty(String field, String newValue) {
|
||||||
public void addRequestProperty(String field, String newValue) {
|
|
||||||
delegate.addRequestProperty(field, newValue);
|
delegate.addRequestProperty(field, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String getHeaderField(String key) {
|
||||||
public String getHeaderField(String key) {
|
|
||||||
return delegate.getHeaderField(key);
|
return delegate.getHeaderField(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public long getHeaderFieldDate(String field, long defaultValue) {
|
||||||
public long getHeaderFieldDate(String field, long defaultValue) {
|
|
||||||
return delegate.getHeaderFieldDate(field, defaultValue);
|
return delegate.getHeaderFieldDate(field, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public int getHeaderFieldInt(String field, int defaultValue) {
|
||||||
public int getHeaderFieldInt(String field, int defaultValue) {
|
|
||||||
return delegate.getHeaderFieldInt(field, defaultValue);
|
return delegate.getHeaderFieldInt(field, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String getHeaderFieldKey(int position) {
|
||||||
public String getHeaderFieldKey(int position) {
|
|
||||||
return delegate.getHeaderFieldKey(position);
|
return delegate.getHeaderFieldKey(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public long getIfModifiedSince() {
|
||||||
public long getIfModifiedSince() {
|
|
||||||
return delegate.getIfModifiedSince();
|
return delegate.getIfModifiedSince();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public InputStream getInputStream() throws IOException {
|
||||||
public InputStream getInputStream() throws IOException {
|
|
||||||
return delegate.getInputStream();
|
return delegate.getInputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public long getLastModified() {
|
||||||
public long getLastModified() {
|
|
||||||
return delegate.getLastModified();
|
return delegate.getLastModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public OutputStream getOutputStream() throws IOException {
|
||||||
public OutputStream getOutputStream() throws IOException {
|
|
||||||
return delegate.getOutputStream();
|
return delegate.getOutputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public Permission getPermission() throws IOException {
|
||||||
public Permission getPermission() throws IOException {
|
|
||||||
return delegate.getPermission();
|
return delegate.getPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String getRequestProperty(String field) {
|
||||||
public String getRequestProperty(String field) {
|
|
||||||
return delegate.getRequestProperty(field);
|
return delegate.getRequestProperty(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public URL getURL() {
|
||||||
public URL getURL() {
|
|
||||||
return delegate.getURL();
|
return delegate.getURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public boolean getUseCaches() {
|
||||||
public boolean getUseCaches() {
|
|
||||||
return delegate.getUseCaches();
|
return delegate.getUseCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setAllowUserInteraction(boolean newValue) {
|
||||||
public void setAllowUserInteraction(boolean newValue) {
|
|
||||||
delegate.setAllowUserInteraction(newValue);
|
delegate.setAllowUserInteraction(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setDefaultUseCaches(boolean newValue) {
|
||||||
public void setDefaultUseCaches(boolean newValue) {
|
|
||||||
delegate.setDefaultUseCaches(newValue);
|
delegate.setDefaultUseCaches(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setDoInput(boolean newValue) {
|
||||||
public void setDoInput(boolean newValue) {
|
|
||||||
delegate.setDoInput(newValue);
|
delegate.setDoInput(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setDoOutput(boolean newValue) {
|
||||||
public void setDoOutput(boolean newValue) {
|
|
||||||
delegate.setDoOutput(newValue);
|
delegate.setDoOutput(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setIfModifiedSince(long newValue) {
|
||||||
public void setIfModifiedSince(long newValue) {
|
|
||||||
delegate.setIfModifiedSince(newValue);
|
delegate.setIfModifiedSince(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setRequestProperty(String field, String newValue) {
|
||||||
public void setRequestProperty(String field, String newValue) {
|
|
||||||
delegate.setRequestProperty(field, newValue);
|
delegate.setRequestProperty(field, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setUseCaches(boolean newValue) {
|
||||||
public void setUseCaches(boolean newValue) {
|
|
||||||
delegate.setUseCaches(newValue);
|
delegate.setUseCaches(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setConnectTimeout(int timeoutMillis) {
|
||||||
public void setConnectTimeout(int timeoutMillis) {
|
|
||||||
delegate.setConnectTimeout(timeoutMillis);
|
delegate.setConnectTimeout(timeoutMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public int getConnectTimeout() {
|
||||||
public int getConnectTimeout() {
|
|
||||||
return delegate.getConnectTimeout();
|
return delegate.getConnectTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setReadTimeout(int timeoutMillis) {
|
||||||
public void setReadTimeout(int timeoutMillis) {
|
|
||||||
delegate.setReadTimeout(timeoutMillis);
|
delegate.setReadTimeout(timeoutMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public int getReadTimeout() {
|
||||||
public int getReadTimeout() {
|
|
||||||
return delegate.getReadTimeout();
|
return delegate.getReadTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public String toString() {
|
||||||
public String toString() {
|
|
||||||
return delegate.toString();
|
return delegate.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setFixedLengthStreamingMode(int contentLength) {
|
||||||
public void setFixedLengthStreamingMode(int contentLength) {
|
|
||||||
delegate.setFixedLengthStreamingMode(contentLength);
|
delegate.setFixedLengthStreamingMode(contentLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void setChunkedStreamingMode(int chunkLength) {
|
||||||
public void setChunkedStreamingMode(int chunkLength) {
|
|
||||||
delegate.setChunkedStreamingMode(chunkLength);
|
delegate.setChunkedStreamingMode(chunkLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
|
@Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
|
||||||
delegate.hostnameVerifier = hostnameVerifier;
|
delegate.client.setHostnameVerifier(hostnameVerifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public HostnameVerifier getHostnameVerifier() {
|
@Override public HostnameVerifier getHostnameVerifier() {
|
||||||
return delegate.hostnameVerifier;
|
return delegate.client.getHostnameVerifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
|
@Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
|
||||||
delegate.sslSocketFactory = sslSocketFactory;
|
delegate.client.setSslSocketFactory(sslSocketFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public SSLSocketFactory getSSLSocketFactory() {
|
@Override public SSLSocketFactory getSSLSocketFactory() {
|
||||||
return delegate.sslSocketFactory;
|
return delegate.client.getSslSocketFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
@Override public void setFixedLengthStreamingMode(long contentLength) {
|
||||||
|
delegate.setFixedLengthStreamingMode(contentLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
|
private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
|
||||||
private HttpUrlConnectionDelegate(URL url, OkHttpClient client, OkResponseCache responseCache,
|
private HttpUrlConnectionDelegate(URL url, OkHttpClient client) {
|
||||||
Set<Route> failedRoutes) {
|
super(url, client);
|
||||||
super(url, client, responseCache, failedRoutes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override protected HttpURLConnection getHttpConnectionToCache() {
|
@Override public HttpURLConnection getHttpConnectionToCache() {
|
||||||
return HttpsURLConnectionImpl.this;
|
return HttpsURLConnectionImpl.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,45 +363,4 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class HttpsEngine extends HttpEngine {
|
|
||||||
/**
|
|
||||||
* Stash of HttpsEngine.connection.socket to implement requests like
|
|
||||||
* {@link #getCipherSuite} even after the connection has been recycled.
|
|
||||||
*/
|
|
||||||
private SSLSocket sslSocket;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param policy the HttpURLConnectionImpl with connection configuration
|
|
||||||
*/
|
|
||||||
public HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
|
|
||||||
Connection connection, RetryableOutputStream requestBody) throws IOException {
|
|
||||||
super(policy, method, requestHeaders, connection, requestBody);
|
|
||||||
this.sslSocket = connection != null ? (SSLSocket) connection.getSocket() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override protected void connected(Connection connection) {
|
|
||||||
this.sslSocket = (SSLSocket) connection.getSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
|
|
||||||
return cacheResponse instanceof SecureCacheResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override protected boolean includeAuthorityInRequestLine() {
|
|
||||||
// Even if there is a proxy, it isn't involved. Always request just the file.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override protected TunnelRequest getTunnelConfig() {
|
|
||||||
String userAgent = requestHeaders.getUserAgent();
|
|
||||||
if (userAgent == null) {
|
|
||||||
userAgent = getDefaultUserAgent();
|
|
||||||
}
|
|
||||||
|
|
||||||
URL url = policy.getURL();
|
|
||||||
return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent,
|
|
||||||
requestHeaders.getProxyAuthorization());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 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 com.squareup.okhttp.internal.http;
|
|
||||||
|
|
||||||
import com.squareup.okhttp.ResponseSource;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.CacheRequest;
|
|
||||||
import java.net.CacheResponse;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An extended response cache API. Unlike {@link java.net.ResponseCache}, this
|
|
||||||
* interface supports conditional caching and statistics.
|
|
||||||
*
|
|
||||||
* <p>Along with the rest of the {@code internal} package, this is not a public
|
|
||||||
* API. Applications wishing to supply their own caches must use the more
|
|
||||||
* limited {@link java.net.ResponseCache} interface.
|
|
||||||
*/
|
|
||||||
public interface OkResponseCache {
|
|
||||||
CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
CacheRequest put(URI uri, URLConnection urlConnection) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a conditional request hit by updating the stored cache response
|
|
||||||
* with the headers from {@code httpConnection}. The cached response body is
|
|
||||||
* not updated. If the stored response has changed since {@code
|
|
||||||
* conditionalCacheHit} was returned, this does nothing.
|
|
||||||
*/
|
|
||||||
void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException;
|
|
||||||
|
|
||||||
/** Track an conditional GET that was satisfied by this cache. */
|
|
||||||
void trackConditionalCacheHit();
|
|
||||||
|
|
||||||
/** Track an HTTP response being satisfied by {@code source}. */
|
|
||||||
void trackResponse(ResponseSource source);
|
|
||||||
}
|
|
||||||
4
framework/src/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java
Normal file → Executable file
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.squareup.okhttp.internal.http;
|
package com.squareup.okhttp.internal.http;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.OkResponseCache;
|
||||||
import com.squareup.okhttp.ResponseSource;
|
import com.squareup.okhttp.ResponseSource;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.CacheRequest;
|
import java.net.CacheRequest;
|
||||||
@@ -41,6 +42,9 @@ public final class OkResponseCacheAdapter implements OkResponseCache {
|
|||||||
return responseCache.put(uri, urlConnection);
|
return responseCache.put(uri, urlConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public void maybeRemove(String requestMethod, URI uri) throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection connection)
|
@Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection connection)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
}
|
}
|
||||||
|
|||||||
49
framework/src/com/squareup/okhttp/internal/http/Policy.java
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp.internal.http;
|
||||||
|
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public interface Policy {
|
||||||
|
/** Returns true if HTTP response caches should be used. */
|
||||||
|
boolean getUseCaches();
|
||||||
|
|
||||||
|
/** Returns the HttpURLConnection instance to store in the cache. */
|
||||||
|
HttpURLConnection getHttpConnectionToCache();
|
||||||
|
|
||||||
|
/** Returns the current destination URL, possibly a redirect. */
|
||||||
|
URL getURL();
|
||||||
|
|
||||||
|
/** Returns the If-Modified-Since timestamp, or 0 if none is set. */
|
||||||
|
long getIfModifiedSince();
|
||||||
|
|
||||||
|
/** Returns true if a non-direct proxy is specified. */
|
||||||
|
boolean usingProxy();
|
||||||
|
|
||||||
|
/** @see java.net.HttpURLConnection#setChunkedStreamingMode(int) */
|
||||||
|
int getChunkLength();
|
||||||
|
|
||||||
|
/** @see java.net.HttpURLConnection#setFixedLengthStreamingMode(int) */
|
||||||
|
long getFixedContentLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current proxy that this connection is using.
|
||||||
|
* @see java.net.HttpURLConnection#usingProxy
|
||||||
|
*/
|
||||||
|
void setSelectedProxy(Proxy proxy);
|
||||||
|
}
|
||||||
73
framework/src/com/squareup/okhttp/internal/http/RawHeaders.java
Normal file → Executable file
@@ -32,6 +32,7 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The HTTP status and unparsed header fields of a single HTTP message. Values
|
* The HTTP status and unparsed header fields of a single HTTP message. Values
|
||||||
@@ -122,23 +123,6 @@ public final class RawHeaders {
|
|||||||
this.httpMinorVersion = httpMinorVersion;
|
this.httpMinorVersion = httpMinorVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void computeResponseStatusLineFromSpdyHeaders() throws IOException {
|
|
||||||
String status = null;
|
|
||||||
String version = null;
|
|
||||||
for (int i = 0; i < namesAndValues.size(); i += 2) {
|
|
||||||
String name = namesAndValues.get(i);
|
|
||||||
if (":status".equals(name)) {
|
|
||||||
status = namesAndValues.get(i + 1);
|
|
||||||
} else if (":version".equals(name)) {
|
|
||||||
version = namesAndValues.get(i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (status == null || version == null) {
|
|
||||||
throw new ProtocolException("Expected ':status' and ':version' headers not present");
|
|
||||||
}
|
|
||||||
setStatusLine(version + " " + status);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param method like "GET", "POST", "HEAD", etc.
|
* @param method like "GET", "POST", "HEAD", etc.
|
||||||
* @param path like "/foo/bar.html"
|
* @param path like "/foo/bar.html"
|
||||||
@@ -180,14 +164,17 @@ public final class RawHeaders {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an HTTP header line containing a field name, a literal colon, and a
|
* Add an HTTP header line containing a field name, a literal colon, and a
|
||||||
* value.
|
* value. This works around empty header names and header names that start
|
||||||
|
* with a colon (created by old broken SPDY versions of the response cache).
|
||||||
*/
|
*/
|
||||||
public void addLine(String line) {
|
public void addLine(String line) {
|
||||||
int index = line.indexOf(":");
|
int index = line.indexOf(":", 1);
|
||||||
if (index == -1) {
|
if (index != -1) {
|
||||||
addLenient("", line);
|
|
||||||
} else {
|
|
||||||
addLenient(line.substring(0, index), line.substring(index + 1));
|
addLenient(line.substring(0, index), line.substring(index + 1));
|
||||||
|
} else if (line.startsWith(":")) {
|
||||||
|
addLenient("", line.substring(1)); // Empty header name.
|
||||||
|
} else {
|
||||||
|
addLenient("", line); // No header name.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +235,15 @@ public final class RawHeaders {
|
|||||||
return namesAndValues.get(fieldNameIndex);
|
return namesAndValues.get(fieldNameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns an immutable case-insensitive set of header names. */
|
||||||
|
public Set<String> names() {
|
||||||
|
TreeSet<String> result = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
for (int i = 0; i < length(); i++) {
|
||||||
|
result.add(getFieldName(i));
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableSet(result);
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the value at {@code index} or null if that is out of range. */
|
/** Returns the value at {@code index} or null if that is out of range. */
|
||||||
public String getValue(int index) {
|
public String getValue(int index) {
|
||||||
int valueIndex = index * 2 + 1;
|
int valueIndex = index * 2 + 1;
|
||||||
@@ -267,6 +263,20 @@ public final class RawHeaders {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns an immutable list of the header values for {@code name}. */
|
||||||
|
public List<String> values(String name) {
|
||||||
|
List<String> result = null;
|
||||||
|
for (int i = 0; i < length(); i++) {
|
||||||
|
if (name.equalsIgnoreCase(getFieldName(i))) {
|
||||||
|
if (result == null) result = new ArrayList<String>(2);
|
||||||
|
result.add(getValue(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result != null
|
||||||
|
? Collections.unmodifiableList(result)
|
||||||
|
: Collections.<String>emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
/** @param fieldNames a case-insensitive set of HTTP header field names. */
|
/** @param fieldNames a case-insensitive set of HTTP header field names. */
|
||||||
public RawHeaders getAll(Set<String> fieldNames) {
|
public RawHeaders getAll(Set<String> fieldNames) {
|
||||||
RawHeaders result = new RawHeaders();
|
RawHeaders result = new RawHeaders();
|
||||||
@@ -401,10 +411,13 @@ public final class RawHeaders {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RawHeaders fromNameValueBlock(List<String> nameValueBlock) {
|
/** Returns headers for a name value block containing a SPDY response. */
|
||||||
|
public static RawHeaders fromNameValueBlock(List<String> nameValueBlock) throws IOException {
|
||||||
if (nameValueBlock.size() % 2 != 0) {
|
if (nameValueBlock.size() % 2 != 0) {
|
||||||
throw new IllegalArgumentException("Unexpected name value block: " + nameValueBlock);
|
throw new IllegalArgumentException("Unexpected name value block: " + nameValueBlock);
|
||||||
}
|
}
|
||||||
|
String status = null;
|
||||||
|
String version = null;
|
||||||
RawHeaders result = new RawHeaders();
|
RawHeaders result = new RawHeaders();
|
||||||
for (int i = 0; i < nameValueBlock.size(); i += 2) {
|
for (int i = 0; i < nameValueBlock.size(); i += 2) {
|
||||||
String name = nameValueBlock.get(i);
|
String name = nameValueBlock.get(i);
|
||||||
@@ -414,11 +427,21 @@ public final class RawHeaders {
|
|||||||
if (end == -1) {
|
if (end == -1) {
|
||||||
end = values.length();
|
end = values.length();
|
||||||
}
|
}
|
||||||
result.namesAndValues.add(name);
|
String value = values.substring(start, end);
|
||||||
result.namesAndValues.add(values.substring(start, end));
|
if (":status".equals(name)) {
|
||||||
|
status = value;
|
||||||
|
} else if (":version".equals(name)) {
|
||||||
|
version = value;
|
||||||
|
} else {
|
||||||
|
result.namesAndValues.add(name);
|
||||||
|
result.namesAndValues.add(value);
|
||||||
|
}
|
||||||
start = end + 1;
|
start = end + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (status == null) throw new ProtocolException("Expected ':status' header not present");
|
||||||
|
if (version == null) throw new ProtocolException("Expected ':version' header not present");
|
||||||
|
result.setStatusLine(version + " " + status);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
framework/src/com/squareup/okhttp/internal/http/RequestHeaders.java
Normal file → Executable file
@@ -48,7 +48,7 @@ public final class RequestHeaders {
|
|||||||
*/
|
*/
|
||||||
private boolean hasAuthorization;
|
private boolean hasAuthorization;
|
||||||
|
|
||||||
private int contentLength = -1;
|
private long contentLength = -1;
|
||||||
private String transferEncoding;
|
private String transferEncoding;
|
||||||
private String userAgent;
|
private String userAgent;
|
||||||
private String host;
|
private String host;
|
||||||
@@ -157,7 +157,7 @@ public final class RequestHeaders {
|
|||||||
return hasAuthorization;
|
return hasAuthorization;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getContentLength() {
|
public long getContentLength() {
|
||||||
return contentLength;
|
return contentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,14 +205,26 @@ public final class RequestHeaders {
|
|||||||
this.transferEncoding = "chunked";
|
this.transferEncoding = "chunked";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContentLength(int contentLength) {
|
public void setContentLength(long contentLength) {
|
||||||
if (this.contentLength != -1) {
|
if (this.contentLength != -1) {
|
||||||
headers.removeAll("Content-Length");
|
headers.removeAll("Content-Length");
|
||||||
}
|
}
|
||||||
headers.add("Content-Length", Integer.toString(contentLength));
|
headers.add("Content-Length", Long.toString(contentLength));
|
||||||
this.contentLength = contentLength;
|
this.contentLength = contentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the Content-Length headers. Call this when dropping the body on a
|
||||||
|
* request or response, such as when a redirect changes the method from POST
|
||||||
|
* to GET.
|
||||||
|
*/
|
||||||
|
public void removeContentLength() {
|
||||||
|
if (contentLength != -1) {
|
||||||
|
headers.removeAll("Content-Length");
|
||||||
|
contentLength = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setUserAgent(String userAgent) {
|
public void setUserAgent(String userAgent) {
|
||||||
if (this.userAgent != null) {
|
if (this.userAgent != null) {
|
||||||
headers.removeAll("User-Agent");
|
headers.removeAll("User-Agent");
|
||||||
@@ -282,9 +294,24 @@ public final class RequestHeaders {
|
|||||||
public void addCookies(Map<String, List<String>> allCookieHeaders) {
|
public void addCookies(Map<String, List<String>> allCookieHeaders) {
|
||||||
for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
|
for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
|
if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
|
||||||
headers.addAll(key, entry.getValue());
|
&& !entry.getValue().isEmpty()) {
|
||||||
|
headers.add(key, buildCookieHeader(entry.getValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send all cookies in one big header, as recommended by
|
||||||
|
* <a href="http://tools.ietf.org/html/rfc6265#section-4.2.1">RFC 6265</a>.
|
||||||
|
*/
|
||||||
|
private String buildCookieHeader(List<String> cookies) {
|
||||||
|
if (cookies.size() == 1) return cookies.get(0);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < cookies.size(); i++) {
|
||||||
|
if (i > 0) sb.append("; ");
|
||||||
|
sb.append(cookies.get(i));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
framework/src/com/squareup/okhttp/internal/http/ResponseHeaders.java
Normal file → Executable file
@@ -17,6 +17,7 @@
|
|||||||
package com.squareup.okhttp.internal.http;
|
package com.squareup.okhttp.internal.http;
|
||||||
|
|
||||||
import com.squareup.okhttp.ResponseSource;
|
import com.squareup.okhttp.ResponseSource;
|
||||||
|
import com.squareup.okhttp.internal.Platform;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -34,13 +35,16 @@ import static com.squareup.okhttp.internal.Util.equal;
|
|||||||
public final class ResponseHeaders {
|
public final class ResponseHeaders {
|
||||||
|
|
||||||
/** HTTP header name for the local time when the request was sent. */
|
/** HTTP header name for the local time when the request was sent. */
|
||||||
private static final String SENT_MILLIS = "X-Android-Sent-Millis";
|
private static final String SENT_MILLIS = Platform.get().getPrefix() + "-Sent-Millis";
|
||||||
|
|
||||||
/** HTTP header name for the local time when the response was received. */
|
/** HTTP header name for the local time when the response was received. */
|
||||||
private static final String RECEIVED_MILLIS = "X-Android-Received-Millis";
|
private static final String RECEIVED_MILLIS = Platform.get().getPrefix() + "-Received-Millis";
|
||||||
|
|
||||||
/** HTTP synthetic header with the response source. */
|
/** HTTP synthetic header with the response source. */
|
||||||
static final String RESPONSE_SOURCE = "X-Android-Response-Source";
|
static final String RESPONSE_SOURCE = Platform.get().getPrefix() + "-Response-Source";
|
||||||
|
|
||||||
|
/** HTTP synthetic header with the selected transport (spdy/3, http/1.1, etc). */
|
||||||
|
static final String SELECTED_TRANSPORT = Platform.get().getPrefix() + "-Selected-Transport";
|
||||||
|
|
||||||
private final URI uri;
|
private final URI uri;
|
||||||
private final RawHeaders headers;
|
private final RawHeaders headers;
|
||||||
@@ -110,8 +114,9 @@ public final class ResponseHeaders {
|
|||||||
|
|
||||||
private String contentEncoding;
|
private String contentEncoding;
|
||||||
private String transferEncoding;
|
private String transferEncoding;
|
||||||
private int contentLength = -1;
|
private long contentLength = -1;
|
||||||
private String connection;
|
private String connection;
|
||||||
|
private String contentType;
|
||||||
|
|
||||||
public ResponseHeaders(URI uri, RawHeaders headers) {
|
public ResponseHeaders(URI uri, RawHeaders headers) {
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
@@ -168,9 +173,11 @@ public final class ResponseHeaders {
|
|||||||
transferEncoding = value;
|
transferEncoding = value;
|
||||||
} else if ("Content-Length".equalsIgnoreCase(fieldName)) {
|
} else if ("Content-Length".equalsIgnoreCase(fieldName)) {
|
||||||
try {
|
try {
|
||||||
contentLength = Integer.parseInt(value);
|
contentLength = Long.parseLong(value);
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
}
|
}
|
||||||
|
} else if ("Content-Type".equalsIgnoreCase(fieldName)) {
|
||||||
|
contentType = value;
|
||||||
} else if ("Connection".equalsIgnoreCase(fieldName)) {
|
} else if ("Connection".equalsIgnoreCase(fieldName)) {
|
||||||
connection = value;
|
connection = value;
|
||||||
} else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) {
|
} else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) {
|
||||||
@@ -259,10 +266,14 @@ public final class ResponseHeaders {
|
|||||||
return contentEncoding;
|
return contentEncoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getContentLength() {
|
public long getContentLength() {
|
||||||
return contentLength;
|
return contentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
public String getConnection() {
|
public String getConnection() {
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
@@ -278,6 +289,10 @@ public final class ResponseHeaders {
|
|||||||
headers.set(RESPONSE_SOURCE, responseSource.toString() + " " + headers.getResponseCode());
|
headers.set(RESPONSE_SOURCE, responseSource.toString() + " " + headers.getResponseCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTransport(String transport) {
|
||||||
|
headers.set(SELECTED_TRANSPORT, transport);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current age of the response, in milliseconds. The calculation
|
* Returns the current age of the response, in milliseconds. The calculation
|
||||||
* is specified by RFC 2616, 13.2.3 Age Calculations.
|
* is specified by RFC 2616, 13.2.3 Age Calculations.
|
||||||
|
|||||||
0
framework/src/com/squareup/okhttp/internal/http/RetryableOutputStream.java
Normal file → Executable file
28
framework/src/com/squareup/okhttp/internal/http/RouteSelector.java
Normal file → Executable file
@@ -19,6 +19,7 @@ import com.squareup.okhttp.Address;
|
|||||||
import com.squareup.okhttp.Connection;
|
import com.squareup.okhttp.Connection;
|
||||||
import com.squareup.okhttp.ConnectionPool;
|
import com.squareup.okhttp.ConnectionPool;
|
||||||
import com.squareup.okhttp.Route;
|
import com.squareup.okhttp.Route;
|
||||||
|
import com.squareup.okhttp.RouteDatabase;
|
||||||
import com.squareup.okhttp.internal.Dns;
|
import com.squareup.okhttp.internal.Dns;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@@ -32,8 +33,6 @@ import java.util.Iterator;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
|
||||||
|
|
||||||
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ public final class RouteSelector {
|
|||||||
private final ProxySelector proxySelector;
|
private final ProxySelector proxySelector;
|
||||||
private final ConnectionPool pool;
|
private final ConnectionPool pool;
|
||||||
private final Dns dns;
|
private final Dns dns;
|
||||||
private final Set<Route> failedRoutes;
|
private final RouteDatabase routeDatabase;
|
||||||
|
|
||||||
/* The most recently attempted route. */
|
/* The most recently attempted route. */
|
||||||
private Proxy lastProxy;
|
private Proxy lastProxy;
|
||||||
@@ -78,13 +77,13 @@ public final class RouteSelector {
|
|||||||
private final List<Route> postponedRoutes;
|
private final List<Route> postponedRoutes;
|
||||||
|
|
||||||
public RouteSelector(Address address, URI uri, ProxySelector proxySelector, ConnectionPool pool,
|
public RouteSelector(Address address, URI uri, ProxySelector proxySelector, ConnectionPool pool,
|
||||||
Dns dns, Set<Route> failedRoutes) {
|
Dns dns, RouteDatabase routeDatabase) {
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.proxySelector = proxySelector;
|
this.proxySelector = proxySelector;
|
||||||
this.pool = pool;
|
this.pool = pool;
|
||||||
this.dns = dns;
|
this.dns = dns;
|
||||||
this.failedRoutes = failedRoutes;
|
this.routeDatabase = routeDatabase;
|
||||||
this.postponedRoutes = new LinkedList<Route>();
|
this.postponedRoutes = new LinkedList<Route>();
|
||||||
|
|
||||||
resetNextProxy(uri, address.getProxy());
|
resetNextProxy(uri, address.getProxy());
|
||||||
@@ -103,11 +102,11 @@ public final class RouteSelector {
|
|||||||
*
|
*
|
||||||
* @throws NoSuchElementException if there are no more routes to attempt.
|
* @throws NoSuchElementException if there are no more routes to attempt.
|
||||||
*/
|
*/
|
||||||
public Connection next() throws IOException {
|
public Connection next(String method) throws IOException {
|
||||||
// Always prefer pooled connections over new connections.
|
// Always prefer pooled connections over new connections.
|
||||||
Connection pooled = pool.get(address);
|
for (Connection pooled; (pooled = pool.get(address)) != null; ) {
|
||||||
if (pooled != null) {
|
if (method.equals("GET") || pooled.isReadable()) return pooled;
|
||||||
return pooled;
|
pooled.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the next route to attempt.
|
// Compute the next route to attempt.
|
||||||
@@ -128,11 +127,11 @@ public final class RouteSelector {
|
|||||||
|
|
||||||
boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;
|
boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;
|
||||||
Route route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);
|
Route route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);
|
||||||
if (failedRoutes.contains(route)) {
|
if (routeDatabase.shouldPostpone(route)) {
|
||||||
postponedRoutes.add(route);
|
postponedRoutes.add(route);
|
||||||
// We will only recurse in order to skip previously failed routes. They will be
|
// We will only recurse in order to skip previously failed routes. They will be
|
||||||
// tried last.
|
// tried last.
|
||||||
return next();
|
return next(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Connection(route);
|
return new Connection(route);
|
||||||
@@ -149,12 +148,7 @@ public final class RouteSelector {
|
|||||||
proxySelector.connectFailed(uri, failedRoute.getProxy().address(), failure);
|
proxySelector.connectFailed(uri, failedRoute.getProxy().address(), failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
failedRoutes.add(failedRoute);
|
routeDatabase.failed(failedRoute, failure);
|
||||||
if (!(failure instanceof SSLHandshakeException)) {
|
|
||||||
// If the problem was not related to SSL then it will also fail with
|
|
||||||
// a different Tls mode therefore we can be proactive about it.
|
|
||||||
failedRoutes.add(failedRoute.flipTlsMode());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resets {@link #nextProxy} to the first option. */
|
/** Resets {@link #nextProxy} to the first option. */
|
||||||
|
|||||||
21
framework/src/com/squareup/okhttp/internal/http/SpdyTransport.java
Normal file → Executable file
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.squareup.okhttp.internal.http;
|
package com.squareup.okhttp.internal.http;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.internal.spdy.ErrorCode;
|
||||||
import com.squareup.okhttp.internal.spdy.SpdyConnection;
|
import com.squareup.okhttp.internal.spdy.SpdyConnection;
|
||||||
import com.squareup.okhttp.internal.spdy.SpdyStream;
|
import com.squareup.okhttp.internal.spdy.SpdyStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -36,6 +37,10 @@ public final class SpdyTransport implements Transport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override public OutputStream createRequestBody() throws IOException {
|
@Override public OutputStream createRequestBody() throws IOException {
|
||||||
|
long fixedContentLength = httpEngine.policy.getFixedContentLength();
|
||||||
|
if (fixedContentLength != -1) {
|
||||||
|
httpEngine.requestHeaders.setContentLength(fixedContentLength);
|
||||||
|
}
|
||||||
// TODO: if we aren't streaming up to the server, we should buffer the whole request
|
// TODO: if we aren't streaming up to the server, we should buffer the whole request
|
||||||
writeRequestHeaders();
|
writeRequestHeaders();
|
||||||
return stream.getOutputStream();
|
return stream.getOutputStream();
|
||||||
@@ -55,7 +60,7 @@ public final class SpdyTransport implements Transport {
|
|||||||
boolean hasResponseBody = true;
|
boolean hasResponseBody = true;
|
||||||
stream = spdyConnection.newStream(requestHeaders.toNameValueBlock(), hasRequestBody,
|
stream = spdyConnection.newStream(requestHeaders.toNameValueBlock(), hasRequestBody,
|
||||||
hasResponseBody);
|
hasResponseBody);
|
||||||
stream.setReadTimeout(httpEngine.policy.getReadTimeout());
|
stream.setReadTimeout(httpEngine.client.getReadTimeout());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void writeRequestBody(RetryableOutputStream requestBody) throws IOException {
|
@Override public void writeRequestBody(RetryableOutputStream requestBody) throws IOException {
|
||||||
@@ -69,24 +74,26 @@ public final class SpdyTransport implements Transport {
|
|||||||
@Override public ResponseHeaders readResponseHeaders() throws IOException {
|
@Override public ResponseHeaders readResponseHeaders() throws IOException {
|
||||||
List<String> nameValueBlock = stream.getResponseHeaders();
|
List<String> nameValueBlock = stream.getResponseHeaders();
|
||||||
RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
|
RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
|
||||||
rawHeaders.computeResponseStatusLineFromSpdyHeaders();
|
|
||||||
httpEngine.receiveHeaders(rawHeaders);
|
httpEngine.receiveHeaders(rawHeaders);
|
||||||
return new ResponseHeaders(httpEngine.uri, rawHeaders);
|
|
||||||
|
ResponseHeaders headers = new ResponseHeaders(httpEngine.uri, rawHeaders);
|
||||||
|
headers.setTransport("spdy/3");
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException {
|
@Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException {
|
||||||
return new UnknownLengthHttpInputStream(stream.getInputStream(), cacheRequest, httpEngine);
|
return new UnknownLengthHttpInputStream(stream.getInputStream(), cacheRequest, httpEngine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut,
|
@Override public boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
|
||||||
InputStream responseBodyIn) {
|
InputStream responseBodyIn) {
|
||||||
if (streamCancelled) {
|
if (streamCanceled) {
|
||||||
if (stream != null) {
|
if (stream != null) {
|
||||||
stream.closeLater(SpdyStream.RST_CANCEL);
|
stream.closeLater(ErrorCode.CANCEL);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// If stream is null, it either means that writeRequestHeaders wasn't called
|
// If stream is null, it either means that writeRequestHeaders wasn't called
|
||||||
// or that SpdyConnection#newStream threw an IOEXception. In both cases there's
|
// or that SpdyConnection#newStream threw an IOException. In both cases there's
|
||||||
// nothing to do here and this stream can't be reused.
|
// nothing to do here and this stream can't be reused.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
2
framework/src/com/squareup/okhttp/internal/http/Transport.java
Normal file → Executable file
@@ -59,6 +59,6 @@ interface Transport {
|
|||||||
InputStream getTransferStream(CacheRequest cacheRequest) throws IOException;
|
InputStream getTransferStream(CacheRequest cacheRequest) throws IOException;
|
||||||
|
|
||||||
/** Returns true if the underlying connection can be recycled. */
|
/** Returns true if the underlying connection can be recycled. */
|
||||||
boolean makeReusable(boolean streamReusable, OutputStream requestBodyOut,
|
boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
|
||||||
InputStream responseBodyIn);
|
InputStream responseBodyIn);
|
||||||
}
|
}
|
||||||
|
|||||||
6
framework/src/com/squareup/okhttp/internal/http/UnknownLengthHttpInputStream.java
Normal file → Executable file
@@ -25,9 +25,9 @@ import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
|
|||||||
final class UnknownLengthHttpInputStream extends AbstractHttpInputStream {
|
final class UnknownLengthHttpInputStream extends AbstractHttpInputStream {
|
||||||
private boolean inputExhausted;
|
private boolean inputExhausted;
|
||||||
|
|
||||||
UnknownLengthHttpInputStream(InputStream is, CacheRequest cacheRequest, HttpEngine httpEngine)
|
UnknownLengthHttpInputStream(InputStream in, CacheRequest cacheRequest, HttpEngine httpEngine)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
super(is, httpEngine, cacheRequest);
|
super(in, httpEngine, cacheRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int read(byte[] buffer, int offset, int count) throws IOException {
|
@Override public int read(byte[] buffer, int offset, int count) throws IOException {
|
||||||
@@ -39,7 +39,7 @@ final class UnknownLengthHttpInputStream extends AbstractHttpInputStream {
|
|||||||
int read = in.read(buffer, offset, count);
|
int read = in.read(buffer, offset, count);
|
||||||
if (read == -1) {
|
if (read == -1) {
|
||||||
inputExhausted = true;
|
inputExhausted = true;
|
||||||
endOfInput(false);
|
endOfInput();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
cacheWrite(buffer, offset, read);
|
cacheWrite(buffer, offset, read);
|
||||||
|
|||||||
67
framework/src/com/squareup/okhttp/internal/spdy/ErrorCode.java
Executable file
@@ -0,0 +1,67 @@
|
|||||||
|
package com.squareup.okhttp.internal.spdy;
|
||||||
|
|
||||||
|
public enum ErrorCode {
|
||||||
|
/** Not an error! For SPDY stream resets, prefer null over NO_ERROR. */
|
||||||
|
NO_ERROR(0, -1, 0),
|
||||||
|
|
||||||
|
PROTOCOL_ERROR(1, 1, 1),
|
||||||
|
|
||||||
|
/** A subtype of PROTOCOL_ERROR used by SPDY. */
|
||||||
|
INVALID_STREAM(1, 2, -1),
|
||||||
|
|
||||||
|
/** A subtype of PROTOCOL_ERROR used by SPDY. */
|
||||||
|
UNSUPPORTED_VERSION(1, 4, -1),
|
||||||
|
|
||||||
|
/** A subtype of PROTOCOL_ERROR used by SPDY. */
|
||||||
|
STREAM_IN_USE(1, 8, -1),
|
||||||
|
|
||||||
|
/** A subtype of PROTOCOL_ERROR used by SPDY. */
|
||||||
|
STREAM_ALREADY_CLOSED(1, 9, -1),
|
||||||
|
|
||||||
|
INTERNAL_ERROR(2, 6, 2),
|
||||||
|
|
||||||
|
FLOW_CONTROL_ERROR(3, 7, -1),
|
||||||
|
|
||||||
|
STREAM_CLOSED(5, -1, -1),
|
||||||
|
|
||||||
|
FRAME_TOO_LARGE(6, 11, -1),
|
||||||
|
|
||||||
|
REFUSED_STREAM(7, 3, -1),
|
||||||
|
|
||||||
|
CANCEL(8, 5, -1),
|
||||||
|
|
||||||
|
COMPRESSION_ERROR(9, -1, -1),
|
||||||
|
|
||||||
|
INVALID_CREDENTIALS(-1, 10, -1);
|
||||||
|
|
||||||
|
public final int httpCode;
|
||||||
|
public final int spdyRstCode;
|
||||||
|
public final int spdyGoAwayCode;
|
||||||
|
|
||||||
|
private ErrorCode(int httpCode, int spdyRstCode, int spdyGoAwayCode) {
|
||||||
|
this.httpCode = httpCode;
|
||||||
|
this.spdyRstCode = spdyRstCode;
|
||||||
|
this.spdyGoAwayCode = spdyGoAwayCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ErrorCode fromSpdy3Rst(int code) {
|
||||||
|
for (ErrorCode errorCode : ErrorCode.values()) {
|
||||||
|
if (errorCode.spdyRstCode == code) return errorCode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ErrorCode fromHttp2(int code) {
|
||||||
|
for (ErrorCode errorCode : ErrorCode.values()) {
|
||||||
|
if (errorCode.httpCode == code) return errorCode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ErrorCode fromSpdyGoAway(int code) {
|
||||||
|
for (ErrorCode errorCode : ErrorCode.values()) {
|
||||||
|
if (errorCode.spdyGoAwayCode == code) return errorCode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
framework/src/com/squareup/okhttp/internal/spdy/FrameReader.java
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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 com.squareup.okhttp.internal.spdy;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Reads transport frames for SPDY/3 or HTTP/2.0. */
|
||||||
|
public interface FrameReader extends Closeable {
|
||||||
|
void readConnectionHeader() throws IOException;
|
||||||
|
boolean nextFrame(Handler handler) throws IOException;
|
||||||
|
|
||||||
|
public interface Handler {
|
||||||
|
void data(boolean inFinished, int streamId, InputStream in, int length) throws IOException;
|
||||||
|
/**
|
||||||
|
* Create or update incoming headers, creating the corresponding streams
|
||||||
|
* if necessary. Frames that trigger this are SPDY SYN_STREAM, HEADERS, and
|
||||||
|
* SYN_REPLY, and HTTP/2.0 HEADERS and PUSH_PROMISE.
|
||||||
|
*
|
||||||
|
* @param inFinished true if the sender will not send further frames.
|
||||||
|
* @param outFinished true if the receiver should not send further frames.
|
||||||
|
* @param streamId the stream owning these headers.
|
||||||
|
* @param associatedStreamId the stream that triggered the sender to create
|
||||||
|
* this stream.
|
||||||
|
* @param priority or -1 for no priority. For SPDY, priorities range from 0
|
||||||
|
* (highest) thru 7 (lowest). For HTTP/2.0, priorities range from 0
|
||||||
|
* (highest) thru 2**31-1 (lowest).
|
||||||
|
*/
|
||||||
|
void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
|
||||||
|
int priority, List<String> nameValueBlock, HeadersMode headersMode);
|
||||||
|
void rstStream(int streamId, ErrorCode errorCode);
|
||||||
|
void settings(boolean clearPrevious, Settings settings);
|
||||||
|
void noop();
|
||||||
|
void ping(boolean reply, int payload1, int payload2);
|
||||||
|
void goAway(int lastGoodStreamId, ErrorCode errorCode);
|
||||||
|
void windowUpdate(int streamId, int deltaWindowSize, boolean endFlowControl);
|
||||||
|
void priority(int streamId, int priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
framework/src/com/squareup/okhttp/internal/spdy/FrameWriter.java
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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 com.squareup.okhttp.internal.spdy;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Writes transport frames for SPDY/3 or HTTP/2.0. */
|
||||||
|
public interface FrameWriter extends Closeable {
|
||||||
|
/** HTTP/2.0 only. */
|
||||||
|
void connectionHeader() throws IOException;
|
||||||
|
|
||||||
|
/** SPDY/3 only. */
|
||||||
|
void flush() throws IOException;
|
||||||
|
void synStream(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
|
||||||
|
int priority, int slot, List<String> nameValueBlock) throws IOException;
|
||||||
|
void synReply(boolean outFinished, int streamId, List<String> nameValueBlock) throws IOException;
|
||||||
|
void headers(int streamId, List<String> nameValueBlock) throws IOException;
|
||||||
|
void rstStream(int streamId, ErrorCode errorCode) throws IOException;
|
||||||
|
void data(boolean outFinished, int streamId, byte[] data) throws IOException;
|
||||||
|
void data(boolean outFinished, int streamId, byte[] data, int offset, int byteCount)
|
||||||
|
throws IOException;
|
||||||
|
void settings(Settings settings) throws IOException;
|
||||||
|
void noop() throws IOException;
|
||||||
|
void ping(boolean reply, int payload1, int payload2) throws IOException;
|
||||||
|
void goAway(int lastGoodStreamId, ErrorCode errorCode) throws IOException;
|
||||||
|
void windowUpdate(int streamId, int deltaWindowSize) throws IOException;
|
||||||
|
}
|
||||||
49
framework/src/com/squareup/okhttp/internal/spdy/HeadersMode.java
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp.internal.spdy;
|
||||||
|
|
||||||
|
enum HeadersMode {
|
||||||
|
SPDY_SYN_STREAM,
|
||||||
|
SPDY_REPLY,
|
||||||
|
SPDY_HEADERS,
|
||||||
|
HTTP_20_HEADERS;
|
||||||
|
|
||||||
|
/** Returns true if it is an error these headers to create a new stream. */
|
||||||
|
public boolean failIfStreamAbsent() {
|
||||||
|
return this == SPDY_REPLY || this == SPDY_HEADERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if it is an error these headers to update an existing stream. */
|
||||||
|
public boolean failIfStreamPresent() {
|
||||||
|
return this == SPDY_SYN_STREAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if it is an error these headers to be the initial headers of a
|
||||||
|
* response.
|
||||||
|
*/
|
||||||
|
public boolean failIfHeadersAbsent() {
|
||||||
|
return this == SPDY_HEADERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if it is an error these headers to be update existing headers
|
||||||
|
* of a response.
|
||||||
|
*/
|
||||||
|
public boolean failIfHeadersPresent() {
|
||||||
|
return this == SPDY_REPLY;
|
||||||
|
}
|
||||||
|
}
|
||||||
371
framework/src/com/squareup/okhttp/internal/spdy/Hpack.java
Executable file
@@ -0,0 +1,371 @@
|
|||||||
|
package com.squareup.okhttp.internal.spdy;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and write HPACK v03.
|
||||||
|
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03
|
||||||
|
*/
|
||||||
|
final class Hpack {
|
||||||
|
|
||||||
|
static class HeaderEntry {
|
||||||
|
private final String name;
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
HeaderEntry(String name, String value) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This needs to be the length in UTF-8 bytes, not the length in chars.
|
||||||
|
int length() {
|
||||||
|
return 32 + name.length() + value.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final int PREFIX_5_BITS = 0x1f;
|
||||||
|
static final int PREFIX_6_BITS = 0x3f;
|
||||||
|
static final int PREFIX_7_BITS = 0x7f;
|
||||||
|
static final int PREFIX_8_BITS = 0xff;
|
||||||
|
|
||||||
|
static final List<HeaderEntry> INITIAL_CLIENT_TO_SERVER_HEADER_TABLE = Arrays.asList(
|
||||||
|
new HeaderEntry(":scheme", "http"),
|
||||||
|
new HeaderEntry(":scheme", "https"),
|
||||||
|
new HeaderEntry(":host", ""),
|
||||||
|
new HeaderEntry(":path", "/"),
|
||||||
|
new HeaderEntry(":method", "GET"),
|
||||||
|
new HeaderEntry("accept", ""),
|
||||||
|
new HeaderEntry("accept-charset", ""),
|
||||||
|
new HeaderEntry("accept-encoding", ""),
|
||||||
|
new HeaderEntry("accept-language", ""),
|
||||||
|
new HeaderEntry("cookie", ""),
|
||||||
|
new HeaderEntry("if-modified-since", ""),
|
||||||
|
new HeaderEntry("user-agent", ""),
|
||||||
|
new HeaderEntry("referer", ""),
|
||||||
|
new HeaderEntry("authorization", ""),
|
||||||
|
new HeaderEntry("allow", ""),
|
||||||
|
new HeaderEntry("cache-control", ""),
|
||||||
|
new HeaderEntry("connection", ""),
|
||||||
|
new HeaderEntry("content-length", ""),
|
||||||
|
new HeaderEntry("content-type", ""),
|
||||||
|
new HeaderEntry("date", ""),
|
||||||
|
new HeaderEntry("expect", ""),
|
||||||
|
new HeaderEntry("from", ""),
|
||||||
|
new HeaderEntry("if-match", ""),
|
||||||
|
new HeaderEntry("if-none-match", ""),
|
||||||
|
new HeaderEntry("if-range", ""),
|
||||||
|
new HeaderEntry("if-unmodified-since", ""),
|
||||||
|
new HeaderEntry("max-forwards", ""),
|
||||||
|
new HeaderEntry("proxy-authorization", ""),
|
||||||
|
new HeaderEntry("range", ""),
|
||||||
|
new HeaderEntry("via", "")
|
||||||
|
);
|
||||||
|
|
||||||
|
static final List<HeaderEntry> INITIAL_SERVER_TO_CLIENT_HEADER_TABLE = Arrays.asList(
|
||||||
|
new HeaderEntry(":status", "200"),
|
||||||
|
new HeaderEntry("age", ""),
|
||||||
|
new HeaderEntry("cache-control", ""),
|
||||||
|
new HeaderEntry("content-length", ""),
|
||||||
|
new HeaderEntry("content-type", ""),
|
||||||
|
new HeaderEntry("date", ""),
|
||||||
|
new HeaderEntry("etag", ""),
|
||||||
|
new HeaderEntry("expires", ""),
|
||||||
|
new HeaderEntry("last-modified", ""),
|
||||||
|
new HeaderEntry("server", ""),
|
||||||
|
new HeaderEntry("set-cookie", ""),
|
||||||
|
new HeaderEntry("vary", ""),
|
||||||
|
new HeaderEntry("via", ""),
|
||||||
|
new HeaderEntry("access-control-allow-origin", ""),
|
||||||
|
new HeaderEntry("accept-ranges", ""),
|
||||||
|
new HeaderEntry("allow", ""),
|
||||||
|
new HeaderEntry("connection", ""),
|
||||||
|
new HeaderEntry("content-disposition", ""),
|
||||||
|
new HeaderEntry("content-encoding", ""),
|
||||||
|
new HeaderEntry("content-language", ""),
|
||||||
|
new HeaderEntry("content-location", ""),
|
||||||
|
new HeaderEntry("content-range", ""),
|
||||||
|
new HeaderEntry("link", ""),
|
||||||
|
new HeaderEntry("location", ""),
|
||||||
|
new HeaderEntry("proxy-authenticate", ""),
|
||||||
|
new HeaderEntry("refresh", ""),
|
||||||
|
new HeaderEntry("retry-after", ""),
|
||||||
|
new HeaderEntry("strict-transport-security", ""),
|
||||||
|
new HeaderEntry("transfer-encoding", ""),
|
||||||
|
new HeaderEntry("www-authenticate", "")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update these when initial tables change to sum of each entry length.
|
||||||
|
static final int INITIAL_CLIENT_TO_SERVER_HEADER_TABLE_LENGTH = 1262;
|
||||||
|
static final int INITIAL_SERVER_TO_CLIENT_HEADER_TABLE_LENGTH = 1304;
|
||||||
|
|
||||||
|
private Hpack() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Reader {
|
||||||
|
private final long maxBufferSize = 4096; // TODO: needs to come from settings.
|
||||||
|
private final DataInputStream in;
|
||||||
|
|
||||||
|
private final BitSet referenceSet = new BitSet();
|
||||||
|
private final List<HeaderEntry> headerTable;
|
||||||
|
private final List<String> emittedHeaders = new ArrayList<String>();
|
||||||
|
private long bufferSize = 0;
|
||||||
|
private long bytesLeft = 0;
|
||||||
|
|
||||||
|
Reader(DataInputStream in, boolean client) {
|
||||||
|
this.in = in;
|
||||||
|
if (client) { // we are reading from the server
|
||||||
|
this.headerTable = new ArrayList<HeaderEntry>(INITIAL_SERVER_TO_CLIENT_HEADER_TABLE);
|
||||||
|
this.bufferSize = INITIAL_SERVER_TO_CLIENT_HEADER_TABLE_LENGTH;
|
||||||
|
} else {
|
||||||
|
this.headerTable = new ArrayList<HeaderEntry>(INITIAL_CLIENT_TO_SERVER_HEADER_TABLE);
|
||||||
|
this.bufferSize = INITIAL_CLIENT_TO_SERVER_HEADER_TABLE_LENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read {@code byteCount} bytes of headers from the source stream into the
|
||||||
|
* set of emitted headers.
|
||||||
|
*/
|
||||||
|
public void readHeaders(int byteCount) throws IOException {
|
||||||
|
bytesLeft += byteCount;
|
||||||
|
// TODO: limit to 'byteCount' bytes?
|
||||||
|
|
||||||
|
while (bytesLeft > 0) {
|
||||||
|
int b = readByte();
|
||||||
|
|
||||||
|
if ((b & 0x80) != 0) {
|
||||||
|
int index = readInt(b, PREFIX_7_BITS);
|
||||||
|
readIndexedHeader(index);
|
||||||
|
} else if (b == 0x60) {
|
||||||
|
readLiteralHeaderWithoutIndexingNewName();
|
||||||
|
} else if ((b & 0xe0) == 0x60) {
|
||||||
|
int index = readInt(b, PREFIX_5_BITS);
|
||||||
|
readLiteralHeaderWithoutIndexingIndexedName(index - 1);
|
||||||
|
} else if (b == 0x40) {
|
||||||
|
readLiteralHeaderWithIncrementalIndexingNewName();
|
||||||
|
} else if ((b & 0xe0) == 0x40) {
|
||||||
|
int index = readInt(b, PREFIX_5_BITS);
|
||||||
|
readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
|
||||||
|
} else if (b == 0) {
|
||||||
|
readLiteralHeaderWithSubstitutionIndexingNewName();
|
||||||
|
} else if ((b & 0xc0) == 0) {
|
||||||
|
int index = readInt(b, PREFIX_6_BITS);
|
||||||
|
readLiteralHeaderWithSubstitutionIndexingIndexedName(index - 1);
|
||||||
|
} else {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void emitReferenceSet() {
|
||||||
|
for (int i = referenceSet.nextSetBit(0); i != -1; i = referenceSet.nextSetBit(i + 1)) {
|
||||||
|
emittedHeaders.add(getName(i));
|
||||||
|
emittedHeaders.add(getValue(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all headers emitted since they were last cleared, then clears the
|
||||||
|
* emitted headers.
|
||||||
|
*/
|
||||||
|
public List<String> getAndReset() {
|
||||||
|
List<String> result = new ArrayList<String>(emittedHeaders);
|
||||||
|
emittedHeaders.clear();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readIndexedHeader(int index) {
|
||||||
|
if (referenceSet.get(index)) {
|
||||||
|
referenceSet.clear(index);
|
||||||
|
} else {
|
||||||
|
referenceSet.set(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readLiteralHeaderWithoutIndexingIndexedName(int index)
|
||||||
|
throws IOException {
|
||||||
|
String name = getName(index);
|
||||||
|
String value = readString();
|
||||||
|
emittedHeaders.add(name);
|
||||||
|
emittedHeaders.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readLiteralHeaderWithoutIndexingNewName()
|
||||||
|
throws IOException {
|
||||||
|
String name = readString();
|
||||||
|
String value = readString();
|
||||||
|
emittedHeaders.add(name);
|
||||||
|
emittedHeaders.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)
|
||||||
|
throws IOException {
|
||||||
|
String name = getName(nameIndex);
|
||||||
|
String value = readString();
|
||||||
|
int index = headerTable.size(); // append to tail
|
||||||
|
insertIntoHeaderTable(index, new HeaderEntry(name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
|
||||||
|
String name = readString();
|
||||||
|
String value = readString();
|
||||||
|
int index = headerTable.size(); // append to tail
|
||||||
|
insertIntoHeaderTable(index, new HeaderEntry(name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readLiteralHeaderWithSubstitutionIndexingIndexedName(int nameIndex)
|
||||||
|
throws IOException {
|
||||||
|
int index = readInt(readByte(), PREFIX_8_BITS);
|
||||||
|
String name = getName(nameIndex);
|
||||||
|
String value = readString();
|
||||||
|
insertIntoHeaderTable(index, new HeaderEntry(name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readLiteralHeaderWithSubstitutionIndexingNewName() throws IOException {
|
||||||
|
String name = readString();
|
||||||
|
int index = readInt(readByte(), PREFIX_8_BITS);
|
||||||
|
String value = readString();
|
||||||
|
insertIntoHeaderTable(index, new HeaderEntry(name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getName(int index) {
|
||||||
|
return headerTable.get(index).name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getValue(int index) {
|
||||||
|
return headerTable.get(index).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertIntoHeaderTable(int index, HeaderEntry entry) {
|
||||||
|
int delta = entry.length();
|
||||||
|
if (index != headerTable.size()) {
|
||||||
|
delta -= headerTable.get(index).length();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the new or replacement header is too big, drop all entries.
|
||||||
|
if (delta > maxBufferSize) {
|
||||||
|
headerTable.clear();
|
||||||
|
bufferSize = 0;
|
||||||
|
// emit the large header to the callback.
|
||||||
|
emittedHeaders.add(entry.name);
|
||||||
|
emittedHeaders.add(entry.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune headers to the required length.
|
||||||
|
while (bufferSize + delta > maxBufferSize) {
|
||||||
|
remove(0);
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0) { // we pruned it, so insert at beginning
|
||||||
|
index = 0;
|
||||||
|
headerTable.add(index, entry);
|
||||||
|
} else if (index == headerTable.size()) { // append to the end
|
||||||
|
headerTable.add(index, entry);
|
||||||
|
} else { // replace value at same position
|
||||||
|
headerTable.set(index, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferSize += delta;
|
||||||
|
referenceSet.set(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove(int index) {
|
||||||
|
bufferSize -= headerTable.remove(index).length();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readByte() throws IOException {
|
||||||
|
bytesLeft--;
|
||||||
|
return in.readByte() & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
int readInt(int firstByte, int prefixMask) throws IOException {
|
||||||
|
int prefix = firstByte & prefixMask;
|
||||||
|
if (prefix < prefixMask) {
|
||||||
|
return prefix; // This was a single byte value.
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a multibyte value. Read 7 bits at a time.
|
||||||
|
int result = prefixMask;
|
||||||
|
int shift = 0;
|
||||||
|
while (true) {
|
||||||
|
int b = readByte();
|
||||||
|
if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255].
|
||||||
|
result += (b & 0x7f) << shift;
|
||||||
|
shift += 7;
|
||||||
|
} else {
|
||||||
|
result += b << shift; // Last byte.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a UTF-8 encoded string. Since ASCII is a subset of UTF-8, this method
|
||||||
|
* may be used to read strings that are known to be ASCII-only.
|
||||||
|
*/
|
||||||
|
public String readString() throws IOException {
|
||||||
|
int firstByte = readByte();
|
||||||
|
int length = readInt(firstByte, PREFIX_8_BITS);
|
||||||
|
byte[] encoded = new byte[length];
|
||||||
|
bytesLeft -= length;
|
||||||
|
in.readFully(encoded);
|
||||||
|
return new String(encoded, "UTF-8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Writer {
|
||||||
|
private final OutputStream out;
|
||||||
|
|
||||||
|
Writer(OutputStream out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeHeaders(List<String> nameValueBlock) throws IOException {
|
||||||
|
// TODO: implement a compression strategy.
|
||||||
|
for (int i = 0, size = nameValueBlock.size(); i < size; i += 2) {
|
||||||
|
out.write(0x60); // Literal Header without Indexing - New Name.
|
||||||
|
writeString(nameValueBlock.get(i));
|
||||||
|
writeString(nameValueBlock.get(i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeInt(int value, int prefixMask, int bits) throws IOException {
|
||||||
|
// Write the raw value for a single byte value.
|
||||||
|
if (value < prefixMask) {
|
||||||
|
out.write(bits | value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the mask to start a multibyte value.
|
||||||
|
out.write(bits | prefixMask);
|
||||||
|
value -= prefixMask;
|
||||||
|
|
||||||
|
// Write 7 bits at a time 'til we're done.
|
||||||
|
while (value >= 0x80) {
|
||||||
|
int b = value & 0x7f;
|
||||||
|
out.write(b | 0x80);
|
||||||
|
value >>>= 7;
|
||||||
|
}
|
||||||
|
out.write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a UTF-8 encoded string. Since ASCII is a subset of UTF-8, this
|
||||||
|
* method can be used to write strings that are known to be ASCII-only.
|
||||||
|
*/
|
||||||
|
public void writeString(String headerName) throws IOException {
|
||||||
|
byte[] bytes = headerName.getBytes("UTF-8");
|
||||||
|
writeInt(bytes.length, PREFIX_8_BITS, 0);
|
||||||
|
out.write(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
385
framework/src/com/squareup/okhttp/internal/spdy/Http20Draft06.java
Executable file
@@ -0,0 +1,385 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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 com.squareup.okhttp.internal.spdy;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.internal.Util;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and write http/2 v06 frames.
|
||||||
|
* http://tools.ietf.org/html/draft-ietf-httpbis-http2-06
|
||||||
|
*/
|
||||||
|
final class Http20Draft06 implements Variant {
|
||||||
|
private static final byte[] CONNECTION_HEADER;
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
CONNECTION_HEADER = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final int TYPE_DATA = 0x0;
|
||||||
|
static final int TYPE_HEADERS = 0x1;
|
||||||
|
static final int TYPE_PRIORITY = 0x2;
|
||||||
|
static final int TYPE_RST_STREAM = 0x3;
|
||||||
|
static final int TYPE_SETTINGS = 0x4;
|
||||||
|
static final int TYPE_PUSH_PROMISE = 0x5;
|
||||||
|
static final int TYPE_PING = 0x6;
|
||||||
|
static final int TYPE_GOAWAY = 0x7;
|
||||||
|
static final int TYPE_WINDOW_UPDATE = 0x9;
|
||||||
|
static final int TYPE_CONTINUATION = 0xa;
|
||||||
|
|
||||||
|
static final int FLAG_END_STREAM = 0x1;
|
||||||
|
/** Used for headers, push-promise and continuation. */
|
||||||
|
static final int FLAG_END_HEADERS = 0x4;
|
||||||
|
static final int FLAG_PRIORITY = 0x8;
|
||||||
|
static final int FLAG_PONG = 0x1;
|
||||||
|
static final int FLAG_END_FLOW_CONTROL = 0x1;
|
||||||
|
|
||||||
|
@Override public FrameReader newReader(InputStream in, boolean client) {
|
||||||
|
return new Reader(in, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public FrameWriter newWriter(OutputStream out, boolean client) {
|
||||||
|
return new Writer(out, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Reader implements FrameReader {
|
||||||
|
private final DataInputStream in;
|
||||||
|
private final boolean client;
|
||||||
|
private final Hpack.Reader hpackReader;
|
||||||
|
|
||||||
|
Reader(InputStream in, boolean client) {
|
||||||
|
this.in = new DataInputStream(in);
|
||||||
|
this.client = client;
|
||||||
|
this.hpackReader = new Hpack.Reader(this.in, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void readConnectionHeader() throws IOException {
|
||||||
|
if (client) return; // Nothing to read; servers don't send connection headers!
|
||||||
|
byte[] connectionHeader = new byte[CONNECTION_HEADER.length];
|
||||||
|
in.readFully(connectionHeader);
|
||||||
|
if (!Arrays.equals(connectionHeader, CONNECTION_HEADER)) {
|
||||||
|
throw ioException("Expected a connection header but was "
|
||||||
|
+ Arrays.toString(connectionHeader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean nextFrame(Handler handler) throws IOException {
|
||||||
|
int w1;
|
||||||
|
try {
|
||||||
|
w1 = in.readInt();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false; // This might be a normal socket close.
|
||||||
|
}
|
||||||
|
int w2 = in.readInt();
|
||||||
|
|
||||||
|
int length = (w1 & 0xffff0000) >> 16;
|
||||||
|
int type = (w1 & 0xff00) >> 8;
|
||||||
|
int flags = w1 & 0xff;
|
||||||
|
// boolean r = (w2 & 0x80000000) != 0; // Reserved.
|
||||||
|
int streamId = (w2 & 0x7fffffff);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case TYPE_DATA:
|
||||||
|
readData(handler, flags, length, streamId);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TYPE_HEADERS:
|
||||||
|
readHeaders(handler, flags, length, streamId);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TYPE_PRIORITY:
|
||||||
|
readPriority(handler, flags, length, streamId);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TYPE_RST_STREAM:
|
||||||
|
readRstStream(handler, flags, length, streamId);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TYPE_SETTINGS:
|
||||||
|
readSettings(handler, flags, length, streamId);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TYPE_PUSH_PROMISE:
|
||||||
|
readPushPromise(handler, flags, length, streamId);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TYPE_PING:
|
||||||
|
readPing(handler, flags, length, streamId);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TYPE_GOAWAY:
|
||||||
|
readGoAway(handler, flags, length, streamId);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case TYPE_WINDOW_UPDATE:
|
||||||
|
readWindowUpdate(handler, flags, length, streamId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readHeaders(Handler handler, int flags, int length, int streamId)
|
||||||
|
throws IOException {
|
||||||
|
if (streamId == 0) throw ioException("TYPE_HEADERS streamId == 0");
|
||||||
|
|
||||||
|
boolean inFinished = (flags & FLAG_END_STREAM) != 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
hpackReader.readHeaders(length);
|
||||||
|
|
||||||
|
if ((flags & FLAG_END_HEADERS) != 0) {
|
||||||
|
hpackReader.emitReferenceSet();
|
||||||
|
List<String> namesAndValues = hpackReader.getAndReset();
|
||||||
|
int priority = -1; // TODO: priority
|
||||||
|
handler.headers(false, inFinished, streamId, -1, priority, namesAndValues,
|
||||||
|
HeadersMode.HTTP_20_HEADERS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read another continuation frame.
|
||||||
|
int w1 = in.readInt();
|
||||||
|
int w2 = in.readInt();
|
||||||
|
|
||||||
|
length = (w1 & 0xffff0000) >> 16;
|
||||||
|
int newType = (w1 & 0xff00) >> 8;
|
||||||
|
flags = w1 & 0xff;
|
||||||
|
|
||||||
|
// TODO: remove in draft 8: CONTINUATION no longer sets END_STREAM
|
||||||
|
inFinished = (flags & FLAG_END_STREAM) != 0;
|
||||||
|
|
||||||
|
// boolean u = (w2 & 0x80000000) != 0; // Unused.
|
||||||
|
int newStreamId = (w2 & 0x7fffffff);
|
||||||
|
|
||||||
|
if (newType != TYPE_CONTINUATION) {
|
||||||
|
throw ioException("TYPE_CONTINUATION didn't have FLAG_END_HEADERS");
|
||||||
|
}
|
||||||
|
if (newStreamId != streamId) throw ioException("TYPE_CONTINUATION streamId changed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readData(Handler handler, int flags, int length, int streamId) throws IOException {
|
||||||
|
boolean inFinished = (flags & FLAG_END_STREAM) != 0;
|
||||||
|
handler.data(inFinished, streamId, in, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readPriority(Handler handler, int flags, int length, int streamId)
|
||||||
|
throws IOException {
|
||||||
|
if (length != 4) throw ioException("TYPE_PRIORITY length: %d != 4", length);
|
||||||
|
if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
|
||||||
|
int w1 = in.readInt();
|
||||||
|
// boolean r = (w1 & 0x80000000) != 0; // Reserved.
|
||||||
|
int priority = (w1 & 0x7fffffff);
|
||||||
|
handler.priority(streamId, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readRstStream(Handler handler, int flags, int length, int streamId)
|
||||||
|
throws IOException {
|
||||||
|
if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length);
|
||||||
|
if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0");
|
||||||
|
int errorCodeInt = in.readInt();
|
||||||
|
ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
|
||||||
|
if (errorCode == null) {
|
||||||
|
throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
|
||||||
|
}
|
||||||
|
handler.rstStream(streamId, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readSettings(Handler handler, int flags, int length, int streamId)
|
||||||
|
throws IOException {
|
||||||
|
if (length % 8 != 0) throw ioException("TYPE_SETTINGS length %% 8 != 0: %s", length);
|
||||||
|
if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
|
||||||
|
Settings settings = new Settings();
|
||||||
|
for (int i = 0; i < length; i += 8) {
|
||||||
|
int w1 = in.readInt();
|
||||||
|
int value = in.readInt();
|
||||||
|
// int r = (w1 & 0xff000000) >>> 24; // Reserved.
|
||||||
|
int id = w1 & 0xffffff;
|
||||||
|
settings.set(id, 0, value);
|
||||||
|
}
|
||||||
|
handler.settings(false, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readPushPromise(Handler handler, int flags, int length, int streamId) {
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readPing(Handler handler, int flags, int length, int streamId) throws IOException {
|
||||||
|
if (length != 8) throw ioException("TYPE_PING length != 8: %s", length);
|
||||||
|
if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
|
||||||
|
int payload1 = in.readInt();
|
||||||
|
int payload2 = in.readInt();
|
||||||
|
boolean reply = (flags & FLAG_PONG) != 0;
|
||||||
|
handler.ping(reply, payload1, payload2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readGoAway(Handler handler, int flags, int length, int streamId)
|
||||||
|
throws IOException {
|
||||||
|
if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
|
||||||
|
int lastStreamId = in.readInt();
|
||||||
|
int errorCodeInt = in.readInt();
|
||||||
|
int opaqueDataLength = length - 8;
|
||||||
|
ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
|
||||||
|
if (errorCode == null) {
|
||||||
|
throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
|
||||||
|
}
|
||||||
|
if (Util.skipByReading(in, opaqueDataLength) != opaqueDataLength) {
|
||||||
|
throw new IOException("TYPE_GOAWAY opaque data was truncated");
|
||||||
|
}
|
||||||
|
handler.goAway(lastStreamId, errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readWindowUpdate(Handler handler, int flags, int length, int streamId)
|
||||||
|
throws IOException {
|
||||||
|
int w1 = in.readInt();
|
||||||
|
// boolean r = (w1 & 0x80000000) != 0; // Reserved.
|
||||||
|
int windowSizeIncrement = (w1 & 0x7fffffff);
|
||||||
|
boolean endFlowControl = (flags & FLAG_END_FLOW_CONTROL) != 0;
|
||||||
|
handler.windowUpdate(streamId, windowSizeIncrement, endFlowControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IOException ioException(String message, Object... args) throws IOException {
|
||||||
|
throw new IOException(String.format(message, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void close() throws IOException {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class Writer implements FrameWriter {
|
||||||
|
private final DataOutputStream out;
|
||||||
|
private final boolean client;
|
||||||
|
private final ByteArrayOutputStream hpackBuffer;
|
||||||
|
private final Hpack.Writer hpackWriter;
|
||||||
|
|
||||||
|
Writer(OutputStream out, boolean client) {
|
||||||
|
this.out = new DataOutputStream(out);
|
||||||
|
this.client = client;
|
||||||
|
this.hpackBuffer = new ByteArrayOutputStream();
|
||||||
|
this.hpackWriter = new Hpack.Writer(hpackBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void flush() throws IOException {
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void connectionHeader() throws IOException {
|
||||||
|
if (!client) return; // Nothing to write; servers don't send connection headers!
|
||||||
|
out.write(CONNECTION_HEADER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void synStream(boolean outFinished, boolean inFinished,
|
||||||
|
int streamId, int associatedStreamId, int priority, int slot, List<String> nameValueBlock)
|
||||||
|
throws IOException {
|
||||||
|
if (inFinished) throw new UnsupportedOperationException();
|
||||||
|
headers(outFinished, streamId, priority, nameValueBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void synReply(boolean outFinished, int streamId,
|
||||||
|
List<String> nameValueBlock) throws IOException {
|
||||||
|
headers(outFinished, streamId, -1, nameValueBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void headers(int streamId, List<String> nameValueBlock)
|
||||||
|
throws IOException {
|
||||||
|
headers(false, streamId, -1, nameValueBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void headers(boolean outFinished, int streamId, int priority,
|
||||||
|
List<String> nameValueBlock) throws IOException {
|
||||||
|
hpackBuffer.reset();
|
||||||
|
hpackWriter.writeHeaders(nameValueBlock);
|
||||||
|
int type = TYPE_HEADERS;
|
||||||
|
// TODO: implement CONTINUATION
|
||||||
|
int length = hpackBuffer.size();
|
||||||
|
int flags = FLAG_END_HEADERS;
|
||||||
|
if (outFinished) flags |= FLAG_END_STREAM;
|
||||||
|
if (priority != -1) flags |= FLAG_PRIORITY;
|
||||||
|
out.writeInt((length & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
|
||||||
|
out.writeInt(streamId & 0x7fffffff);
|
||||||
|
if (priority != -1) out.writeInt(priority & 0x7fffffff);
|
||||||
|
hpackBuffer.writeTo(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
|
||||||
|
throws IOException {
|
||||||
|
throw new UnsupportedOperationException("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void data(boolean outFinished, int streamId, byte[] data) throws IOException {
|
||||||
|
data(outFinished, streamId, data, 0, data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void data(boolean outFinished, int streamId, byte[] data,
|
||||||
|
int offset, int byteCount) throws IOException {
|
||||||
|
int type = TYPE_DATA;
|
||||||
|
int flags = 0;
|
||||||
|
if (outFinished) flags |= FLAG_END_STREAM;
|
||||||
|
out.writeInt((byteCount & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
|
||||||
|
out.writeInt(streamId & 0x7fffffff);
|
||||||
|
out.write(data, offset, byteCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void settings(Settings settings) throws IOException {
|
||||||
|
int type = TYPE_SETTINGS;
|
||||||
|
int length = settings.size() * 8;
|
||||||
|
int flags = 0;
|
||||||
|
int streamId = 0;
|
||||||
|
out.writeInt((length & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
|
||||||
|
out.writeInt(streamId & 0x7fffffff);
|
||||||
|
for (int i = 0; i < Settings.COUNT; i++) {
|
||||||
|
if (!settings.isSet(i)) continue;
|
||||||
|
out.writeInt(i & 0xffffff);
|
||||||
|
out.writeInt(settings.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void noop() throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void ping(boolean reply, int payload1, int payload2)
|
||||||
|
throws IOException {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode)
|
||||||
|
throws IOException {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void windowUpdate(int streamId, int deltaWindowSize)
|
||||||
|
throws IOException {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void close() throws IOException {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||