Use jitpack libraries
This commit is contained in:
1 parent
d186a6ac3b
commit
e3113083bc
136 files changed
+473
-21172
No files matched your search
-12
@@ -1,12 +0,0 @@
|
||||
[submodule "yorozuya"]
|
||||
path = yorozuya
|
||||
url = https://github.com/seven332/Yorozuya
|
||||
[submodule "easyrecyclerview"]
|
||||
path = easyrecyclerview
|
||||
url = https://github.com/seven332/EasyRecyclerView
|
||||
[submodule "beerbelly"]
|
||||
path = beerbelly
|
||||
url = https://github.com/seven332/BeerBelly
|
||||
[submodule "conaco"]
|
||||
path = conaco
|
||||
url = https://github.com/seven332/Conaco
|
||||
+10
-7
@@ -55,24 +55,27 @@ clean {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':beerbelly')
|
||||
compile project(':conaco')
|
||||
compile project(':easyrecyclerview')
|
||||
compile project(':okhttp3')
|
||||
compile project(':yorozuya')
|
||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
||||
testCompile 'junit:junit:4.12'
|
||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
||||
compile 'com.android.support:design:23.3.0'
|
||||
compile 'com.github.amlcurran.showcaseview:library:5.4.3'
|
||||
compile 'com.github.anrwatchdog:anrwatchdog:1.2.0'
|
||||
compile 'com.github.seven332:beerbelly:0.1.1'
|
||||
compile 'com.github.seven332:conaco:0.1.2-eh'
|
||||
compile 'com.github.seven332:easyrecyclerview:0.1.0'
|
||||
compile 'com.github.seven332:drawerLayout:0.1.0'
|
||||
compile 'com.github.seven332:image:0.1.0'
|
||||
compile 'com.github.seven332:image:0.1.2'
|
||||
compile 'com.github.seven332:okhttp:3.2.0-eh'
|
||||
compile 'com.github.seven332:refreshlayout:0.1.0'
|
||||
compile 'com.github.seven332:ripple:0.1.1'
|
||||
compile 'com.github.seven332:streampipe:0.1.0'
|
||||
compile 'com.github.seven332:unifile:0.1.3'
|
||||
compile 'com.github.seven332:yorozuya-collect:0.1.2'
|
||||
compile 'com.github.seven332:yorozuya-thread:0.1.0'
|
||||
compile 'com.github.seven332:yorozuya:0.1.0'
|
||||
compile 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.8.7'
|
||||
compile 'org.ccil.cowan.tagsoup:tagsoup:1.2.1'
|
||||
compile 'org.greenrobot:greendao:2.2.0'
|
||||
compile 'org.jsoup:jsoup:1.9.1'
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
@@ -24,6 +24,9 @@ import android.support.v7.graphics.drawable.DrawableWrapper;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
/**
|
||||
* Show a part of the original drawable
|
||||
*/
|
||||
public class PreciselyClipDrawable extends DrawableWrapper {
|
||||
|
||||
private final boolean mClip;
|
||||
|
||||
@@ -16,26 +16,59 @@
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.widget.TextView;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.conaco.ConacoTask;
|
||||
import com.hippo.conaco.Unikery;
|
||||
import com.hippo.conaco.ValueHolder;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.image.ImageDrawable;
|
||||
import com.hippo.image.RecycledException;
|
||||
import com.hippo.widget.ObservedTextView;
|
||||
|
||||
public class UnikeryDrawable extends WrapDrawable implements Unikery<Bitmap> {
|
||||
public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap>,
|
||||
ObservedTextView.OnWindowAttachListener {
|
||||
|
||||
private static final String TAG = UnikeryDrawable.class.getSimpleName();
|
||||
|
||||
private int mTaskId = Unikery.INVALID_ID;
|
||||
|
||||
private final TextView mTextView;
|
||||
private final ObservedTextView mTextView;
|
||||
private final Conaco<ImageBitmap> mConaco;
|
||||
private String mUrl;
|
||||
|
||||
private ValueHolder<Bitmap> mHolder;
|
||||
|
||||
public UnikeryDrawable(TextView textView) {
|
||||
public UnikeryDrawable(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
|
||||
mTextView = textView;
|
||||
mTextView.setOnWindowAttachListener(this);
|
||||
mConaco = conaco;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
load(mUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
mConaco.cancel(this);
|
||||
clearDrawable();
|
||||
}
|
||||
|
||||
public void load(String url) {
|
||||
if (url != null) {
|
||||
mUrl = url;
|
||||
mConaco.load(new ConacoTask.Builder<ImageBitmap>().setUnikery(this).setUrl(url).setKey(url));
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDrawable() {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable instanceof ImageDrawable) {
|
||||
((ImageDrawable) drawable).recycle();
|
||||
}
|
||||
setDrawable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,9 +87,7 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<Bitmap> {
|
||||
|
||||
updateBounds();
|
||||
if (drawable != null) {
|
||||
// mTextView.requestLayout() not work!
|
||||
CharSequence cs = mTextView.getText();
|
||||
mTextView.setText(cs);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,49 +102,41 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<Bitmap> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMiss(Conaco.Source source) {
|
||||
public void invalidateSelf() {
|
||||
CharSequence cs = mTextView.getText();
|
||||
mTextView.setText(cs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequest() {
|
||||
}
|
||||
public void onMiss(int source) {}
|
||||
|
||||
@Override
|
||||
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
||||
}
|
||||
public void onRequest() {}
|
||||
|
||||
private void removeDrawableAndHolder() {
|
||||
// Remove drawable
|
||||
setDrawable(null);
|
||||
@Override
|
||||
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {}
|
||||
|
||||
// Remove holder
|
||||
if (mHolder != null) {
|
||||
mHolder.release(this);
|
||||
mHolder = null;
|
||||
@Override
|
||||
public void onWait() {}
|
||||
|
||||
@Override
|
||||
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
|
||||
ImageDrawable drawable;
|
||||
try {
|
||||
drawable = new ImageDrawable(value);
|
||||
} catch (RecycledException e) {
|
||||
Log.d(TAG, "The ImageBitmap is recycled", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGetObject(@NonNull ValueHolder<Bitmap> holder, Conaco.Source source) {
|
||||
holder.obtain(this);
|
||||
|
||||
removeDrawableAndHolder();
|
||||
|
||||
mHolder = holder;
|
||||
Drawable drawable = new BitmapDrawable(mTextView.getResources(), holder.getValue());
|
||||
clearDrawable();
|
||||
|
||||
setDrawable(drawable);
|
||||
drawable.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetDrawable(Drawable drawable) {
|
||||
removeDrawableAndHolder();
|
||||
|
||||
setDrawable(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
// Empty
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.content.ComponentCallbacks2;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Debug;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -41,6 +40,7 @@ import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.network.StatusCodeException;
|
||||
import com.hippo.okhttp.CookieDB;
|
||||
import com.hippo.scene.SceneApplication;
|
||||
@@ -79,8 +79,8 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx
|
||||
private EhCookieStore mEhCookieStore;
|
||||
private EhClient mEhClient;
|
||||
private OkHttpClient mOkHttpClient;
|
||||
private BitmapHelper mBitmapHelper;
|
||||
private Conaco<Bitmap> mConaco;
|
||||
private ImageBitmapHelper mImageBitmapHelper;
|
||||
private Conaco<ImageBitmap> mConaco;
|
||||
private LruCache<Long, GalleryDetail> mGalleryDetailCache;
|
||||
private SimpleDiskCache mSpiderInfoCache;
|
||||
private DownloadManager mDownloadManager;
|
||||
@@ -194,7 +194,7 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx
|
||||
|
||||
public void clearMemoryCache() {
|
||||
if (null != mConaco) {
|
||||
mConaco.clearMemoryCache();
|
||||
mConaco.getBeerBelly().clearMemory();
|
||||
}
|
||||
if (null != mGalleryDetailCache) {
|
||||
mGalleryDetailCache.evictAll();
|
||||
@@ -275,12 +275,12 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static BitmapHelper getBitmapHelper(@NonNull Context context) {
|
||||
public static ImageBitmapHelper getImageBitmapHelper(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mBitmapHelper == null) {
|
||||
application.mBitmapHelper = new BitmapHelper();
|
||||
if (application.mImageBitmapHelper == null) {
|
||||
application.mImageBitmapHelper = new ImageBitmapHelper();
|
||||
}
|
||||
return application.mBitmapHelper;
|
||||
return application.mImageBitmapHelper;
|
||||
}
|
||||
|
||||
private static int getMemoryCacheMaxSize(Context context) {
|
||||
@@ -291,17 +291,17 @@ public class EhApplication extends SceneApplication implements Thread.UncaughtEx
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Conaco<Bitmap> getConaco(@NonNull Context context) {
|
||||
public static Conaco<ImageBitmap> getConaco(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mConaco == null) {
|
||||
Conaco.Builder<Bitmap> builder = new Conaco.Builder<>();
|
||||
Conaco.Builder<ImageBitmap> builder = new Conaco.Builder<>();
|
||||
builder.hasMemoryCache = true;
|
||||
builder.memoryCacheMaxSize = getMemoryCacheMaxSize(context);
|
||||
builder.hasDiskCache = true;
|
||||
builder.diskCacheDir = new File(context.getCacheDir(), "thumb");
|
||||
builder.diskCacheMaxSize = 80 * 1024 * 1024; // 80MB
|
||||
builder.okHttpClient = getOkHttpClient(context);
|
||||
builder.objectHelper = getBitmapHelper(context);
|
||||
builder.objectHelper = getImageBitmapHelper(context);
|
||||
builder.debug = DEBUG_CONACO;
|
||||
application.mConaco = builder.build();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import com.hippo.util.SqlUtils;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.sparse.SparseJLArray;
|
||||
import com.hippo.yorozuya.collect.SparseJLArray;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
+29
-12
@@ -16,25 +16,31 @@
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.hippo.conaco.ValueHelper;
|
||||
import com.hippo.conaco.ValueHolder;
|
||||
import com.hippo.yorozuya.io.InputStreamPipe;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class BitmapHelper implements ValueHelper<Bitmap> {
|
||||
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
|
||||
private static final int MAX_CACHE_SIZE = 512 * 512;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Bitmap decode(@NonNull InputStreamPipe isPipe) {
|
||||
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
return BitmapFactory.decodeStream(isPipe.open());
|
||||
Image image = Image.decode(isPipe.open(), false);
|
||||
if (image != null) {
|
||||
return new ImageBitmap(image);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
@@ -46,15 +52,26 @@ public class BitmapHelper implements ValueHelper<Bitmap> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeOf(@NonNull String key, @NonNull Bitmap value) {
|
||||
return value.getByteCount();
|
||||
public int sizeOf(@NonNull String key, @NonNull ImageBitmap value) {
|
||||
return value.getWidth() * value.getHeight() * 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(@NonNull String key, @NonNull ValueHolder<Bitmap> oldValue) {}
|
||||
public void onAddToMemoryCache(@NonNull ImageBitmap oldValue) {
|
||||
oldValue.obtain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useMemoryCache(@NonNull String key, ValueHolder<Bitmap> holder) {
|
||||
return true;
|
||||
public void onRemoveFromMemoryCache(@NonNull String key, @NonNull ImageBitmap oldValue) {
|
||||
oldValue.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useMemoryCache(@NonNull String key, ImageBitmap value) {
|
||||
if (value != null) {
|
||||
return value.getWidth() * value.getHeight() <= MAX_CACHE_SIZE;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ import android.os.AsyncTask;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.client.exception.CancelledException;
|
||||
import com.hippo.yorozuya.PriorityThreadFactory;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@@ -21,7 +21,7 @@ import android.os.Parcelable;
|
||||
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory;
|
||||
import com.hippo.widget.LoadImageView;
|
||||
import com.hippo.yorozuya.IntList;
|
||||
import com.hippo.yorozuya.collect.IntList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ package com.hippo.ehviewer.client.data;
|
||||
import android.os.Parcel;
|
||||
|
||||
import com.hippo.widget.LoadImageView;
|
||||
import com.hippo.yorozuya.IntList;
|
||||
import com.hippo.yorozuya.collect.IntList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@ import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.spider.SpiderQueen;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.yorozuya.ConcurrentPool;
|
||||
import com.hippo.yorozuya.LongList;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.sparse.SparseIJArray;
|
||||
import com.hippo.yorozuya.sparse.SparseJLArray;
|
||||
import com.hippo.yorozuya.collect.LongList;
|
||||
import com.hippo.yorozuya.collect.SparseIJArray;
|
||||
import com.hippo.yorozuya.collect.SparseJLArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -40,10 +40,10 @@ import com.hippo.ehviewer.ui.scene.DownloadsScene;
|
||||
import com.hippo.scene.StageActivity;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.LongList;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.sparse.SparseJBArray;
|
||||
import com.hippo.yorozuya.sparse.SparseJLArray;
|
||||
import com.hippo.yorozuya.collect.LongList;
|
||||
import com.hippo.yorozuya.collect.SparseJBArray;
|
||||
import com.hippo.yorozuya.collect.SparseJLArray;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@@ -28,8 +28,8 @@ import com.hippo.image.Image;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.PriorityThread;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
|
||||
@@ -26,8 +26,8 @@ import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.gallery.gl.GalleryPageView;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.PriorityThread;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
@@ -184,8 +184,8 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
|
||||
|
||||
@Override
|
||||
public void onAttach(GalleryView.Adapter adapter) {
|
||||
AssertUtils.assertEquals("The PagerLayoutManager is attached", mAdapter, null);
|
||||
AssertUtils.assertNotEquals("The adapter is null", adapter, null);
|
||||
AssertUtils.assertNull("The PagerLayoutManager is attached", mAdapter);
|
||||
AssertUtils.assertNotNull("The adapter is null", adapter);
|
||||
mAdapter = adapter;
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
@@ -232,7 +232,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
|
||||
|
||||
@Override
|
||||
public GalleryView.Adapter onDetach() {
|
||||
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null);
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
|
||||
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
@@ -293,7 +293,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
|
||||
public void onFill() {
|
||||
GalleryView.Adapter adapter = mAdapter;
|
||||
GalleryView galleryView = mGalleryView;
|
||||
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", adapter, null);
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", adapter);
|
||||
|
||||
int width = galleryView.getWidth();
|
||||
int height = galleryView.getHeight();
|
||||
@@ -703,7 +703,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
|
||||
|
||||
@Override
|
||||
public void onDataChanged() {
|
||||
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null);
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
|
||||
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
|
||||
@@ -112,8 +112,8 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
|
||||
|
||||
@Override
|
||||
public void onAttach(GalleryView.Adapter adapter) {
|
||||
AssertUtils.assertEquals("The ScrollLayoutManager is attached", mAdapter, null);
|
||||
AssertUtils.assertNotEquals("The iterator is null", adapter, null);
|
||||
AssertUtils.assertNull("The ScrollLayoutManager is attached", mAdapter);
|
||||
AssertUtils.assertNotNull("The iterator is null", adapter);
|
||||
mAdapter = adapter;
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
@@ -151,7 +151,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
|
||||
|
||||
@Override
|
||||
public GalleryView.Adapter onDetach() {
|
||||
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null);
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
|
||||
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
@@ -396,7 +396,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
|
||||
public void onFill() {
|
||||
GalleryView.Adapter adapter = mAdapter;
|
||||
GalleryView galleryView = mGalleryView;
|
||||
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", adapter, null);
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", adapter);
|
||||
|
||||
int size = adapter.size();
|
||||
String errorStr = adapter.getError();
|
||||
@@ -831,7 +831,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
|
||||
|
||||
@Override
|
||||
public void onDataChanged() {
|
||||
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null);
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
|
||||
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
|
||||
@@ -30,14 +30,14 @@ import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider;
|
||||
import com.hippo.io.UniFileInputStreamPipe;
|
||||
import com.hippo.io.UniFileOutputStreamPipe;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
import com.hippo.unifile.FilenameFilter;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.Utilities;
|
||||
import com.hippo.yorozuya.io.InputStreamPipe;
|
||||
import com.hippo.yorozuya.io.OutputStreamPipe;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -48,18 +48,18 @@ import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider;
|
||||
import com.hippo.ehviewer.gallery.gl.GalleryPageView;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.OSUtils;
|
||||
import com.hippo.yorozuya.PriorityThread;
|
||||
import com.hippo.yorozuya.PriorityThreadFactory;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
import com.hippo.yorozuya.Utilities;
|
||||
import com.hippo.yorozuya.io.InputStreamPipe;
|
||||
import com.hippo.yorozuya.io.OutputStreamPipe;
|
||||
import com.hippo.yorozuya.sparse.SparseJLArray;
|
||||
import com.hippo.yorozuya.collect.SparseJLArray;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -86,10 +86,10 @@ import com.hippo.widget.LoadImageView;
|
||||
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.LongList;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.ResourcesUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
import com.hippo.yorozuya.collect.LongList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
|
||||
@@ -74,14 +74,15 @@ import com.hippo.util.TextUrl;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.widget.FabLayout;
|
||||
import com.hippo.widget.LinkifyTextView;
|
||||
import com.hippo.widget.ObservedTextView;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.IntList;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.ResourcesUtils;
|
||||
import com.hippo.yorozuya.SimpleAnimatorListener;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
import com.hippo.yorozuya.collect.IntList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -616,7 +617,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
return new CommentHolder(mInflater.inflate(R.layout.item_gallery_comment, parent, false));
|
||||
}
|
||||
|
||||
public CharSequence generateComment(Context context, TextView textView, GalleryComment comment) {
|
||||
public CharSequence generateComment(Context context, ObservedTextView textView, GalleryComment comment) {
|
||||
SpannableStringBuilder ssb = Html.fromHtml(comment.comment, new URLImageGetter(textView,
|
||||
EhApplication.getConaco(context)), null);
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ import com.hippo.util.ReadableTime;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.widget.AutoWrapLayout;
|
||||
import com.hippo.widget.LoadImageView;
|
||||
import com.hippo.widget.ObservedTextView;
|
||||
import com.hippo.widget.ProgressView;
|
||||
import com.hippo.widget.ProgressiveRatingBar;
|
||||
import com.hippo.widget.SimpleGridAutoSpanLayout;
|
||||
@@ -869,7 +870,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
|
||||
user.setText(comment.user);
|
||||
TextView time = (TextView) v.findViewById(R.id.time);
|
||||
time.setText(ReadableTime.getTimeAgo(comment.time));
|
||||
TextView c = (TextView) v.findViewById(R.id.comment);
|
||||
ObservedTextView c = (ObservedTextView) v.findViewById(R.id.comment);
|
||||
c.setMaxLines(5);
|
||||
c.setText(Html.fromHtml(comment.comment,
|
||||
new URLImageGetter(c, EhApplication.getConaco(context)), null));
|
||||
|
||||
@@ -22,14 +22,14 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.util.SqlUtils;
|
||||
import com.hippo.yorozuya.Say;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchDatabase {
|
||||
public final class SearchDatabase {
|
||||
|
||||
private static final String TAG = SearchDatabase.class.getSimpleName();
|
||||
|
||||
@@ -41,7 +41,7 @@ public class SearchDatabase {
|
||||
|
||||
private static final int MAX_HISTORY = 100;
|
||||
|
||||
private SQLiteDatabase mDatabase;
|
||||
private final SQLiteDatabase mDatabase;
|
||||
|
||||
private static SearchDatabase sInstance;
|
||||
|
||||
@@ -127,11 +127,10 @@ public class SearchDatabase {
|
||||
}
|
||||
mDatabase.delete(TABLE_SUGGESTIONS, selection, null);
|
||||
} catch (RuntimeException e) {
|
||||
Say.e(TAG, "truncateHistory", e);
|
||||
Log.e(TAG, "truncateHistory", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the database. This version has extra support for using the version field
|
||||
* as a mode flags field, and configures the database columns depending on the mode bits
|
||||
|
||||
@@ -24,8 +24,8 @@ import android.opengl.GLUtils;
|
||||
import android.opengl.Matrix;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.yorozuya.IntArray;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.collect.IntList;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -166,7 +166,7 @@ public class GLES20Canvas implements GLCanvas {
|
||||
// Keep track of restore state
|
||||
private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE];
|
||||
private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE];
|
||||
private IntArray mSaveFlags = new IntArray();
|
||||
private final IntList mSaveFlags = new IntList();
|
||||
|
||||
private int mCurrentAlphaIndex = 0;
|
||||
private int mCurrentMatrixIndex = 0;
|
||||
@@ -176,20 +176,20 @@ public class GLES20Canvas implements GLCanvas {
|
||||
private int mHeight;
|
||||
|
||||
// Projection matrix
|
||||
private float[] mProjectionMatrix = new float[MATRIX_SIZE];
|
||||
private final float[] mProjectionMatrix = new float[MATRIX_SIZE];
|
||||
|
||||
// Screen size for when we aren't bound to a texture
|
||||
private int mScreenWidth;
|
||||
private int mScreenHeight;
|
||||
|
||||
// GL programs
|
||||
private int mDrawProgram;
|
||||
private int mTextureProgram;
|
||||
private int mOesTextureProgram;
|
||||
private int mMeshProgram;
|
||||
private final int mDrawProgram;
|
||||
private final int mTextureProgram;
|
||||
private final int mOesTextureProgram;
|
||||
private final int mMeshProgram;
|
||||
|
||||
// GL buffer containing BOX_COORDINATES
|
||||
private int mBoxCoordinates;
|
||||
private final int mBoxCoordinates;
|
||||
|
||||
// Handle indices -- common
|
||||
private static final int INDEX_POSITION = 0;
|
||||
@@ -268,8 +268,8 @@ public class GLES20Canvas implements GLCanvas {
|
||||
new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
|
||||
};
|
||||
|
||||
private final IntArray mUnboundTextures = new IntArray();
|
||||
private final IntArray mDeleteBuffers = new IntArray();
|
||||
private final IntList mUnboundTextures = new IntList();
|
||||
private final IntList mDeleteBuffers = new IntList();
|
||||
|
||||
// Keep track of statistics for debugging
|
||||
private int mCountDrawMesh = 0;
|
||||
@@ -279,10 +279,10 @@ public class GLES20Canvas implements GLCanvas {
|
||||
|
||||
// Buffer for framebuffer IDs -- we keep track so we can switch the attached
|
||||
// texture.
|
||||
private int[] mFrameBuffer = new int[1];
|
||||
private final int[] mFrameBuffer = new int[1];
|
||||
|
||||
// Bound textures.
|
||||
private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>();
|
||||
private final ArrayList<RawTexture> mTargetTextures = new ArrayList<>();
|
||||
|
||||
// Temporary variables used within calculations
|
||||
private final float[] mTempMatrix = new float[32];
|
||||
@@ -492,7 +492,7 @@ public class GLES20Canvas implements GLCanvas {
|
||||
|
||||
@Override
|
||||
public void restore() {
|
||||
int restoreFlags = mSaveFlags.removeLast();
|
||||
int restoreFlags = mSaveFlags.removeAt(mSaveFlags.size() - 1);
|
||||
boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
|
||||
if (restoreAlpha) {
|
||||
mCurrentAlphaIndex--;
|
||||
@@ -869,7 +869,7 @@ public class GLES20Canvas implements GLCanvas {
|
||||
@Override
|
||||
public void deleteRecycledResources() {
|
||||
synchronized (mUnboundTextures) {
|
||||
IntArray ids = mUnboundTextures;
|
||||
IntList ids = mUnboundTextures;
|
||||
if (mUnboundTextures.size() > 0) {
|
||||
mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0);
|
||||
ids.clear();
|
||||
|
||||
@@ -25,8 +25,8 @@ import android.support.annotation.NonNull;
|
||||
import com.hippo.gl.annotation.RenderThread;
|
||||
import com.hippo.gl.view.GLRoot;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.yorozuya.PVLock;
|
||||
import com.hippo.yorozuya.PriorityThreadFactory;
|
||||
import com.hippo.yorozuya.thread.PVLock;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.hippo.gl.util;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.AssertError;
|
||||
|
||||
public class GalleryUtils {
|
||||
|
||||
@@ -51,6 +51,8 @@ public class GalleryUtils {
|
||||
}
|
||||
|
||||
public static void assertInRenderThread() {
|
||||
AssertUtils.assertEquals("Should not do this in render thread", sCurrentThread, Thread.currentThread());
|
||||
if (sCurrentThread != Thread.currentThread()) {
|
||||
throw new AssertError("Should not do this in render thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,20 +23,20 @@ import com.hippo.gl.glrenderer.GLCanvas;
|
||||
import com.hippo.gl.glrenderer.MovableTextTexture;
|
||||
import com.hippo.gl.view.GLView;
|
||||
import com.hippo.gl.view.Gravity;
|
||||
import com.hippo.yorozuya.ArrayUtils;
|
||||
import com.hippo.yorozuya.collect.CollectionUtils;
|
||||
|
||||
public class GLMovableTextView extends GLView {
|
||||
|
||||
MovableTextTexture mTextTexture;
|
||||
|
||||
private String mText = "";
|
||||
private int[] mIndexes = ArrayUtils.EMPTY_INT_ARRAY;
|
||||
private int[] mIndexes = CollectionUtils.EMPTY_INT_ARRAY;
|
||||
|
||||
private int mGravity = Gravity.NO_GRAVITY;
|
||||
|
||||
private void generateIndexes() {
|
||||
if (mTextTexture == null || TextUtils.isEmpty(mText)) {
|
||||
mIndexes = ArrayUtils.EMPTY_INT_ARRAY;
|
||||
mIndexes = CollectionUtils.EMPTY_INT_ARRAY;
|
||||
} else {
|
||||
mIndexes = mTextTexture.getTextIndexes(mText);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ package com.hippo.io;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.io.InputStreamPipe;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -28,7 +28,7 @@ import java.io.InputStream;
|
||||
|
||||
public class FileInputStreamPipe implements InputStreamPipe {
|
||||
|
||||
private File mFile;
|
||||
private final File mFile;
|
||||
private InputStream mIs;
|
||||
|
||||
public FileInputStreamPipe(File file) {
|
||||
|
||||
@@ -18,8 +18,8 @@ package com.hippo.io;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.io.OutputStreamPipe;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -28,7 +28,7 @@ import java.io.OutputStream;
|
||||
|
||||
public class FileOutputStreamPipe implements OutputStreamPipe {
|
||||
|
||||
private File mFile;
|
||||
private final File mFile;
|
||||
private OutputStream mOs;
|
||||
|
||||
public FileOutputStreamPipe(File file) {
|
||||
|
||||
@@ -18,16 +18,16 @@ package com.hippo.io;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.io.InputStreamPipe;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class UniFileInputStreamPipe implements InputStreamPipe {
|
||||
|
||||
private UniFile mFile;
|
||||
private final UniFile mFile;
|
||||
private InputStream mIs;
|
||||
|
||||
public UniFileInputStreamPipe(UniFile file) {
|
||||
|
||||
@@ -18,17 +18,16 @@ package com.hippo.io;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.io.OutputStreamPipe;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class UniFileOutputStreamPipe implements OutputStreamPipe {
|
||||
|
||||
private UniFile mUniFile;
|
||||
|
||||
private final UniFile mUniFile;
|
||||
private OutputStream mOs;
|
||||
|
||||
public UniFileOutputStreamPipe(UniFile uniFile) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.support.v4.app.FragmentActivity;
|
||||
import android.view.View;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.IntList;
|
||||
import com.hippo.yorozuya.collect.IntList;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@@ -16,28 +16,27 @@
|
||||
|
||||
package com.hippo.text;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.conaco.ConacoTask;
|
||||
import com.hippo.drawable.UnikeryDrawable;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.widget.ObservedTextView;
|
||||
|
||||
public class URLImageGetter implements Html.ImageGetter {
|
||||
|
||||
private final TextView mTextView;
|
||||
private final Conaco<Bitmap> mConaco;
|
||||
private final ObservedTextView mTextView;
|
||||
private final Conaco<ImageBitmap> mConaco;
|
||||
|
||||
public URLImageGetter(TextView textView, Conaco<Bitmap> conaco) {
|
||||
public URLImageGetter(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
|
||||
mTextView = textView;
|
||||
mConaco = conaco;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
UnikeryDrawable drawable = new UnikeryDrawable(mTextView);
|
||||
mConaco.load(new ConacoTask.Builder<Bitmap>().setUnikery(drawable).setUrl(source).setKey(source));
|
||||
UnikeryDrawable drawable = new UnikeryDrawable(mTextView, mConaco);
|
||||
drawable.load(source);
|
||||
return drawable;
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,13 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.io.InputStreamPipe;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class BitmapUtils {
|
||||
public final class BitmapUtils {
|
||||
private BitmapUtils() {}
|
||||
|
||||
public static Context sContext;
|
||||
|
||||
|
||||
@@ -41,9 +41,9 @@ import com.hippo.util.DrawableManager;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
import com.hippo.yorozuya.IntList;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.ResourcesUtils;
|
||||
import com.hippo.yorozuya.collect.IntList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -23,9 +23,8 @@ import android.text.Spanned;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class LinkifyTextView extends TextView {
|
||||
public class LinkifyTextView extends ObservedTextView {
|
||||
|
||||
private ClickableSpan mCurrentSpan;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
* Copyright 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,32 +18,48 @@ package com.hippo.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.conaco.ConacoTask;
|
||||
import com.hippo.conaco.DataContainer;
|
||||
import com.hippo.conaco.Unikery;
|
||||
import com.hippo.conaco.ValueHolder;
|
||||
import com.hippo.drawable.PreciselyClipDrawable;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.image.ImageDrawable;
|
||||
import com.hippo.image.RecycledException;
|
||||
|
||||
public class LoadImageView extends FixedAspectImageView implements Unikery<Bitmap>,
|
||||
View.OnClickListener, View.OnLongClickListener {
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public class LoadImageView extends FixedAspectImageView implements Unikery<ImageBitmap>,
|
||||
View.OnClickListener, View.OnLongClickListener, Animatable {
|
||||
|
||||
private static final String TAG = LoadImageView.class.getSimpleName();
|
||||
|
||||
@IntDef({RETRY_TYPE_NONE, RETRY_TYPE_CLICK, RETRY_TYPE_LONG_CLICK})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface RetryType {}
|
||||
|
||||
public static final int RETRY_TYPE_NONE = 0;
|
||||
public static final int RETRY_TYPE_CLICK = 1;
|
||||
public static final int RETRY_TYPE_LONG_CLICK = 2;
|
||||
|
||||
private int mTaskId = Unikery.INVALID_ID;
|
||||
|
||||
private Conaco<Bitmap> mConaco;
|
||||
private Conaco<ImageBitmap> mConaco;
|
||||
|
||||
private String mKey;
|
||||
private String mUrl;
|
||||
@@ -55,49 +71,29 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Bitma
|
||||
private int mClipWidth = Integer.MIN_VALUE;
|
||||
private int mClipHeight = Integer.MIN_VALUE;
|
||||
|
||||
private ValueHolder<Bitmap> mHolder;
|
||||
private int mRetryType;
|
||||
|
||||
private boolean mFailed;
|
||||
|
||||
private RetryType mRetryType = RetryType.NONE;
|
||||
|
||||
private static final RetryType[] sRetryTypeArray = {
|
||||
RetryType.NONE,
|
||||
RetryType.CLICK,
|
||||
RetryType.LONG_CLICK
|
||||
};
|
||||
|
||||
public enum RetryType {
|
||||
NONE (0),
|
||||
CLICK (1),
|
||||
LONG_CLICK (2);
|
||||
|
||||
RetryType(int ni) {
|
||||
nativeInt = ni;
|
||||
}
|
||||
final int nativeInt;
|
||||
}
|
||||
|
||||
public LoadImageView(Context context) {
|
||||
super(context);
|
||||
init(context, null, 0, 0);
|
||||
}
|
||||
|
||||
public LoadImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
init(context, attrs, 0, 0);
|
||||
}
|
||||
|
||||
public LoadImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
public LoadImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context, attrs, defStyle, 0);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs) {
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LoadImageView);
|
||||
final int index = a.getInt(R.styleable.LoadImageView_retryType, -1);
|
||||
if (index >= 0) {
|
||||
setRetryType(sRetryTypeArray[index]);
|
||||
}
|
||||
@SuppressWarnings("WrongConstant")
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LoadImageView, defStyleAttr, defStyleRes);
|
||||
setRetryType(a.getInt(R.styleable.LoadImageView_retryType, 0));
|
||||
a.recycle();
|
||||
|
||||
if (!isInEditMode()) {
|
||||
@@ -105,11 +101,23 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Bitma
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskId(int id) {
|
||||
mTaskId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskId() {
|
||||
return mTaskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (!mFailed) { // Restore if not failed
|
||||
if (mFailed) {
|
||||
onFailure();
|
||||
} else {
|
||||
load(mKey, mUrl, mContainer, mUseNetwork);
|
||||
}
|
||||
}
|
||||
@@ -117,45 +125,73 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Bitma
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
// Cancel
|
||||
mConaco.cancel(this);
|
||||
// Clear drawable
|
||||
clearDrawable();
|
||||
}
|
||||
|
||||
public void setRetryType(RetryType retryType) {
|
||||
if (mRetryType != retryType) {
|
||||
RetryType oldRetryType = mRetryType;
|
||||
mRetryType = retryType;
|
||||
|
||||
if (mFailed) {
|
||||
if (oldRetryType == RetryType.CLICK) {
|
||||
setOnClickListener(null);
|
||||
setClickable(false);
|
||||
} else if (oldRetryType == RetryType.LONG_CLICK) {
|
||||
setOnLongClickListener(null);
|
||||
setLongClickable(false);
|
||||
}
|
||||
|
||||
if (retryType == RetryType.CLICK) {
|
||||
setOnClickListener(this);
|
||||
} else if (retryType == RetryType.LONG_CLICK) {
|
||||
setOnLongClickListener(this);
|
||||
}
|
||||
private ImageDrawable getImageDrawable() {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable instanceof TransitionDrawable) {
|
||||
TransitionDrawable transitionDrawable = (TransitionDrawable) drawable;
|
||||
if (transitionDrawable.getNumberOfLayers() == 2) {
|
||||
drawable = transitionDrawable.getDrawable(1);
|
||||
}
|
||||
}
|
||||
if (drawable instanceof PreciselyClipDrawable) {
|
||||
drawable = ((PreciselyClipDrawable) drawable).getWrappedDrawable();
|
||||
}
|
||||
if (drawable instanceof ImageDrawable) {
|
||||
return (ImageDrawable) drawable;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelRetryType() {
|
||||
if (mRetryType == RetryType.CLICK) {
|
||||
private void clearDrawable() {
|
||||
// Recycle ImageDrawable
|
||||
ImageDrawable imageDrawable = getImageDrawable();
|
||||
if (imageDrawable != null) {
|
||||
imageDrawable.recycle();
|
||||
}
|
||||
|
||||
// Set drawable null
|
||||
setImageDrawable(null);
|
||||
}
|
||||
|
||||
private void clearRetry() {
|
||||
if (mRetryType == RETRY_TYPE_CLICK) {
|
||||
setOnClickListener(null);
|
||||
setClickable(false);
|
||||
} else if (mRetryType == RetryType.LONG_CLICK) {
|
||||
} else if (mRetryType == RETRY_TYPE_LONG_CLICK) {
|
||||
setOnLongClickListener(null);
|
||||
setLongClickable(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void load(String key, String url) {
|
||||
load(key, url, null, true);
|
||||
public void setRetryType(@RetryType int retryType) {
|
||||
if (mRetryType != retryType) {
|
||||
int oldRetryType = mRetryType;
|
||||
mRetryType = retryType;
|
||||
|
||||
if (mFailed) {
|
||||
if (oldRetryType == RETRY_TYPE_CLICK) {
|
||||
setOnClickListener(null);
|
||||
setClickable(false);
|
||||
} else if (oldRetryType == RETRY_TYPE_LONG_CLICK) {
|
||||
setOnLongClickListener(null);
|
||||
setLongClickable(false);
|
||||
}
|
||||
|
||||
if (retryType == RETRY_TYPE_CLICK) {
|
||||
setOnClickListener(this);
|
||||
} else if (retryType == RETRY_TYPE_LONG_CLICK) {
|
||||
setOnLongClickListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setClip(int offsetX, int offsetY, int clipWidth, int clipHeight) {
|
||||
@@ -172,28 +208,28 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Bitma
|
||||
mClipHeight = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
public void load(String key, String url) {
|
||||
load(key, url, null, true);
|
||||
}
|
||||
|
||||
public void load(String key, String url, boolean useNetwork) {
|
||||
load(key, url, null, useNetwork);
|
||||
}
|
||||
|
||||
public void load(String key, String url, DataContainer container, boolean useNetwork) {
|
||||
if (url == null || (key == null && container == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFailed = false;
|
||||
cancelRetryType();
|
||||
|
||||
if (url == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == null && container == null) {
|
||||
return;
|
||||
}
|
||||
clearRetry();
|
||||
|
||||
mKey = key;
|
||||
mUrl = url;
|
||||
mContainer = container;
|
||||
mUseNetwork = useNetwork;
|
||||
|
||||
ConacoTask.Builder<Bitmap> builder = new ConacoTask.Builder<Bitmap>()
|
||||
ConacoTask.Builder<ImageBitmap> builder = new ConacoTask.Builder<ImageBitmap>()
|
||||
.setUnikery(this)
|
||||
.setKey(key)
|
||||
.setUrl(url)
|
||||
@@ -217,66 +253,45 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Bitma
|
||||
mKey = null;
|
||||
mUrl = null;
|
||||
mContainer = null;
|
||||
|
||||
removeDrawableAndHolder();
|
||||
clearDrawable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskId(int id) {
|
||||
mTaskId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskId() {
|
||||
return mTaskId;
|
||||
}
|
||||
|
||||
private void removeDrawableAndHolder() {
|
||||
// Remove drawable
|
||||
setImageDrawable(null);
|
||||
|
||||
// Remove holder
|
||||
if (mHolder != null) {
|
||||
mHolder.release(this);
|
||||
mHolder = null;
|
||||
public void onMiss(int source) {
|
||||
if (source == Conaco.SOURCE_MEMORY) {
|
||||
clearDrawable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMiss(Conaco.Source source) {
|
||||
if (source == Conaco.Source.MEMORY) {
|
||||
removeDrawableAndHolder();
|
||||
public void onRequest() {}
|
||||
|
||||
@Override
|
||||
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {}
|
||||
|
||||
@Override
|
||||
public void onWait() {
|
||||
clearDrawable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
|
||||
Drawable drawable;
|
||||
try {
|
||||
drawable = new ImageDrawable(value);
|
||||
} catch (RecycledException e) {
|
||||
// The image might be recycled because it is removed from memory cache.
|
||||
Log.d(TAG, "The image is recycled", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGetObject(@NonNull ValueHolder<Bitmap> holder, Conaco.Source source) {
|
||||
// Release
|
||||
mKey = null;
|
||||
mUrl = null;
|
||||
mContainer = null;
|
||||
|
||||
holder.obtain(this);
|
||||
|
||||
removeDrawableAndHolder();
|
||||
|
||||
mHolder = holder;
|
||||
Bitmap bitmap = holder.getValue();
|
||||
Drawable drawable = new BitmapDrawable(getResources(), bitmap);
|
||||
clearDrawable();
|
||||
|
||||
if (Integer.MIN_VALUE != mOffsetX) {
|
||||
drawable = new PreciselyClipDrawable(drawable, mOffsetX, mOffsetY, mClipWidth, mClipHeight);
|
||||
}
|
||||
|
||||
if ((source == Conaco.Source.DISK || source == Conaco.Source.NETWORK)) {
|
||||
if ((source == Conaco.SOURCE_DISK || source == Conaco.SOURCE_NETWORK)) {
|
||||
Drawable[] layers = new Drawable[2];
|
||||
layers[0] = new ColorDrawable(Color.TRANSPARENT);
|
||||
layers[1] = drawable;
|
||||
@@ -290,23 +305,14 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Bitma
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetDrawable(Drawable drawable) {
|
||||
mKey = null;
|
||||
mUrl = null;
|
||||
mContainer = null;
|
||||
removeDrawableAndHolder();
|
||||
setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
mFailed = true;
|
||||
removeDrawableAndHolder();
|
||||
clearDrawable();
|
||||
setImageDrawable(getContext().getResources().getDrawable(R.drawable.image_failed));
|
||||
if (mRetryType == RetryType.CLICK) {
|
||||
if (mRetryType == RETRY_TYPE_CLICK) {
|
||||
setOnClickListener(this);
|
||||
} else if (mRetryType == RetryType.LONG_CLICK) {
|
||||
} else if (mRetryType == RETRY_TYPE_LONG_CLICK) {
|
||||
setOnLongClickListener(this);
|
||||
} else {
|
||||
// Can't retry, so release
|
||||
@@ -319,11 +325,32 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Bitma
|
||||
@Override
|
||||
public void onCancel() {
|
||||
mFailed = false;
|
||||
cancelRetryType();
|
||||
// release
|
||||
mKey = null;
|
||||
mUrl = null;
|
||||
mContainer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
ImageDrawable drawable = getImageDrawable();
|
||||
if (drawable != null) {
|
||||
drawable.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
ImageDrawable drawable = getImageDrawable();
|
||||
if (drawable != null) {
|
||||
drawable.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
ImageDrawable drawable = getImageDrawable();
|
||||
if (drawable != null) {
|
||||
return drawable.isRunning();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* 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.hippo.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ObservedTextView extends TextView {
|
||||
|
||||
private OnWindowAttachListener mOnWindowAttachListener;
|
||||
|
||||
public ObservedTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ObservedTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ObservedTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mOnWindowAttachListener != null) {
|
||||
mOnWindowAttachListener.onAttachedToWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mOnWindowAttachListener != null) {
|
||||
mOnWindowAttachListener.onDetachedFromWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnWindowAttachListener(OnWindowAttachListener onWindowAttachListener) {
|
||||
mOnWindowAttachListener = onWindowAttachListener;
|
||||
}
|
||||
|
||||
public interface OnWindowAttachListener {
|
||||
void onAttachedToWindow();
|
||||
void onDetachedFromWindow();
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
android:id="@+id/thumb"
|
||||
android:layout_width="@dimen/gallery_list_thumb_width"
|
||||
android:layout_height="@dimen/gallery_list_thumb_height"
|
||||
app:retryType="none"/>
|
||||
app:retryType="click"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:paddingRight="?attr/listPreferredItemPaddingRight"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="?attr/textAppearanceListItemSmall"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_little_small"/>
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/text2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceListItemSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_small"/>
|
||||
|
||||
</LinearLayout>
|
||||
-1
Submodule beerbelly deleted from 99020f63ac.
-1
Submodule conaco deleted from 27ba4001e4.
Submodule easyrecyclerview deleted from c0ee4edeaf.
@@ -1,4 +0,0 @@
|
||||
/build
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 1
|
||||
targetSdkVersion 23
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
testCompile 'junit:junit:4.12'
|
||||
compile 'com.squareup.okio:okio:1.6.0'
|
||||
}
|
||||
Vendored
-17
@@ -1,17 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in E:\Android\android-sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* 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.okhttp3;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<manifest package="com.squareup.okhttp3">
|
||||
|
||||
<application/>
|
||||
|
||||
</manifest>
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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 okhttp3;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.util.List;
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import okhttp3.internal.Util;
|
||||
|
||||
import static okhttp3.internal.Util.equal;
|
||||
|
||||
/**
|
||||
* A specification for a connection to an origin server. For simple connections, this is the
|
||||
* server's hostname and port. If an explicit proxy is requested (or {@linkplain Proxy#NO_PROXY no
|
||||
* proxy} is explicitly requested), this also includes that proxy information. For secure
|
||||
* connections the address also includes the SSL socket factory, hostname verifier, and certificate
|
||||
* pinner.
|
||||
*
|
||||
* <p>HTTP requests that share the same {@code Address} may also share the same {@link Connection}.
|
||||
*/
|
||||
public final class Address {
|
||||
final HttpUrl url;
|
||||
final Dns dns;
|
||||
final SocketFactory socketFactory;
|
||||
final Authenticator proxyAuthenticator;
|
||||
final List<Protocol> protocols;
|
||||
final List<ConnectionSpec> connectionSpecs;
|
||||
final ProxySelector proxySelector;
|
||||
final Proxy proxy;
|
||||
final SSLSocketFactory sslSocketFactory;
|
||||
final HostnameVerifier hostnameVerifier;
|
||||
final CertificatePinner certificatePinner;
|
||||
|
||||
public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory,
|
||||
SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier,
|
||||
CertificatePinner certificatePinner, Authenticator proxyAuthenticator, Proxy proxy,
|
||||
List<Protocol> protocols, List<ConnectionSpec> connectionSpecs, ProxySelector proxySelector) {
|
||||
this.url = new HttpUrl.Builder()
|
||||
.scheme(sslSocketFactory != null ? "https" : "http")
|
||||
.host(uriHost)
|
||||
.port(uriPort)
|
||||
.build();
|
||||
|
||||
if (dns == null) throw new IllegalArgumentException("dns == null");
|
||||
this.dns = dns;
|
||||
|
||||
if (socketFactory == null) throw new IllegalArgumentException("socketFactory == null");
|
||||
this.socketFactory = socketFactory;
|
||||
|
||||
if (proxyAuthenticator == null) {
|
||||
throw new IllegalArgumentException("proxyAuthenticator == null");
|
||||
}
|
||||
this.proxyAuthenticator = proxyAuthenticator;
|
||||
|
||||
if (protocols == null) throw new IllegalArgumentException("protocols == null");
|
||||
this.protocols = Util.immutableList(protocols);
|
||||
|
||||
if (connectionSpecs == null) throw new IllegalArgumentException("connectionSpecs == null");
|
||||
this.connectionSpecs = Util.immutableList(connectionSpecs);
|
||||
|
||||
if (proxySelector == null) throw new IllegalArgumentException("proxySelector == null");
|
||||
this.proxySelector = proxySelector;
|
||||
|
||||
this.proxy = proxy;
|
||||
this.sslSocketFactory = sslSocketFactory;
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
this.certificatePinner = certificatePinner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL with the hostname and port of the origin server. The path, query, and fragment of
|
||||
* this URL are always empty, since they are not significant for planning a route.
|
||||
*/
|
||||
public HttpUrl url() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/** Returns the service that will be used to resolve IP addresses for hostnames. */
|
||||
public Dns dns() {
|
||||
return dns;
|
||||
}
|
||||
|
||||
/** Returns the socket factory for new connections. */
|
||||
public SocketFactory socketFactory() {
|
||||
return socketFactory;
|
||||
}
|
||||
|
||||
/** Returns the client's proxy authenticator. */
|
||||
public Authenticator proxyAuthenticator() {
|
||||
return proxyAuthenticator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the protocols the client supports. This method always returns a non-null list that
|
||||
* contains minimally {@link Protocol#HTTP_1_1}.
|
||||
*/
|
||||
public List<Protocol> protocols() {
|
||||
return protocols;
|
||||
}
|
||||
|
||||
public List<ConnectionSpec> connectionSpecs() {
|
||||
return connectionSpecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this address's proxy selector. Only used if the proxy is null. If none of this
|
||||
* selector's proxies are reachable, a direct connection will be attempted.
|
||||
*/
|
||||
public ProxySelector proxySelector() {
|
||||
return proxySelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this address's explicitly-specified HTTP proxy, or null to delegate to the {@linkplain
|
||||
* #proxySelector proxy selector}.
|
||||
*/
|
||||
public Proxy proxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/** Returns the SSL socket factory, or null if this is not an HTTPS address. */
|
||||
public SSLSocketFactory sslSocketFactory() {
|
||||
return sslSocketFactory;
|
||||
}
|
||||
|
||||
/** Returns the hostname verifier, or null if this is not an HTTPS address. */
|
||||
public HostnameVerifier hostnameVerifier() {
|
||||
return hostnameVerifier;
|
||||
}
|
||||
|
||||
/** Returns this address's certificate pinner, or null if this is not an HTTPS address. */
|
||||
public CertificatePinner certificatePinner() {
|
||||
return certificatePinner;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
if (other instanceof Address) {
|
||||
Address that = (Address) other;
|
||||
return this.url.equals(that.url)
|
||||
&& this.dns.equals(that.dns)
|
||||
&& this.proxyAuthenticator.equals(that.proxyAuthenticator)
|
||||
&& this.protocols.equals(that.protocols)
|
||||
&& this.connectionSpecs.equals(that.connectionSpecs)
|
||||
&& this.proxySelector.equals(that.proxySelector)
|
||||
&& equal(this.proxy, that.proxy)
|
||||
&& equal(this.sslSocketFactory, that.sslSocketFactory)
|
||||
&& equal(this.hostnameVerifier, that.hostnameVerifier)
|
||||
&& equal(this.certificatePinner, that.certificatePinner);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + url.hashCode();
|
||||
result = 31 * result + dns.hashCode();
|
||||
result = 31 * result + proxyAuthenticator.hashCode();
|
||||
result = 31 * result + protocols.hashCode();
|
||||
result = 31 * result + connectionSpecs.hashCode();
|
||||
result = 31 * result + proxySelector.hashCode();
|
||||
result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
|
||||
result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
|
||||
result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
|
||||
result = 31 * result + (certificatePinner != null ? certificatePinner.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Responds to an authentication challenge from either a remote web server or a proxy server.
|
||||
* Implementations may either attempt to satisfy the challenge by returning a request that includes
|
||||
* an authorization header, or they may refuse the challenge by returning null. In this case the
|
||||
* unauthenticated response will be returned to the caller that triggered it.
|
||||
*
|
||||
* <p>When authentication is requested by an origin server, the response code is 401 and the
|
||||
* implementation should respond with a new request that sets the "Authorization" header.
|
||||
* <pre> {@code
|
||||
*
|
||||
* String credential = Credentials.basic(...)
|
||||
* return response.request().newBuilder()
|
||||
* .header("Authorization", credential)
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Whn authentication is requested by a proxy server, the response code is 407 and the
|
||||
* implementation should respond with a new request that sets the "Proxy-Authorization" header.
|
||||
* <pre> {@code
|
||||
*
|
||||
* String credential = Credentials.basic(...)
|
||||
* return response.request().newBuilder()
|
||||
* .header("Proxy-Authorization", credential)
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Applications may configure OkHttp with an authenticator for origin servers, or proxy servers,
|
||||
* or both.
|
||||
*/
|
||||
public interface Authenticator {
|
||||
/** An authenticator that knows no credentials and makes no attempt to authenticate. */
|
||||
Authenticator NONE = new Authenticator() {
|
||||
@Override public Request authenticate(Route route, Response response) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a request that includes a credential to satisfy an authentication challenge in {@code
|
||||
* response}. Returns null if the challenge cannot be satisfied.
|
||||
*/
|
||||
Request authenticate(Route route, Response response) throws IOException;
|
||||
}
|
||||
@@ -1,742 +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 okhttp3;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import okhttp3.internal.DiskLruCache;
|
||||
import okhttp3.internal.InternalCache;
|
||||
import okhttp3.internal.Util;
|
||||
import okhttp3.internal.http.CacheRequest;
|
||||
import okhttp3.internal.http.CacheStrategy;
|
||||
import okhttp3.internal.http.HttpMethod;
|
||||
import okhttp3.internal.http.OkHeaders;
|
||||
import okhttp3.internal.http.StatusLine;
|
||||
import okhttp3.internal.io.FileSystem;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSink;
|
||||
import okio.BufferedSource;
|
||||
import okio.ByteString;
|
||||
import okio.ForwardingSink;
|
||||
import okio.ForwardingSource;
|
||||
import okio.Okio;
|
||||
import okio.Sink;
|
||||
import okio.Source;
|
||||
|
||||
/**
|
||||
* Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and
|
||||
* bandwidth.
|
||||
*
|
||||
* <h3>Cache Optimization</h3>
|
||||
*
|
||||
* <p>To measure cache effectiveness, this class tracks three statistics:
|
||||
* <ul>
|
||||
* <li><strong>{@linkplain #requestCount() Request Count:}</strong> the number of HTTP
|
||||
* requests issued since this cache was created.
|
||||
* <li><strong>{@linkplain #networkCount() Network Count:}</strong> the number of those
|
||||
* requests that required network use.
|
||||
* <li><strong>{@linkplain #hitCount() Hit Count:}</strong> the number of those requests
|
||||
* whose responses were served by the cache.
|
||||
* </ul>
|
||||
*
|
||||
* Sometimes a request will result in a conditional cache hit. If the cache contains a stale copy of
|
||||
* the response, the client will issue a conditional {@code GET}. The server will then send either
|
||||
* the updated response if it has changed, or a short 'not modified' response if the client's copy
|
||||
* is still valid. Such responses increment both the network count and hit count.
|
||||
*
|
||||
* <p>The best way to improve the cache hit rate is by configuring the web server to return
|
||||
* cacheable responses. Although this client honors all <a
|
||||
* href="http://tools.ietf.org/html/rfc7234">HTTP/1.1 (RFC 7234)</a> cache headers, it doesn't cache
|
||||
* partial responses.
|
||||
*
|
||||
* <h3>Force a Network Response</h3>
|
||||
*
|
||||
* <p>In some situations, such as after a user clicks a 'refresh' button, it may be necessary to
|
||||
* skip the cache, and fetch data directly from the server. To force a full refresh, add the {@code
|
||||
* no-cache} directive: <pre> {@code
|
||||
*
|
||||
* Request request = new Request.Builder()
|
||||
* .cacheControl(new CacheControl.Builder().noCache().build())
|
||||
* .url("http://publicobject.com/helloworld.txt")
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
* If it is only necessary to force a cached response to be validated by the server, use the more
|
||||
* efficient {@code max-age=0} directive instead: <pre> {@code
|
||||
*
|
||||
* Request request = new Request.Builder()
|
||||
* .cacheControl(new CacheControl.Builder()
|
||||
* .maxAge(0, TimeUnit.SECONDS)
|
||||
* .build())
|
||||
* .url("http://publicobject.com/helloworld.txt")
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Force a Cache Response</h3>
|
||||
*
|
||||
* <p>Sometimes you'll want to show resources if they are available immediately, but not otherwise.
|
||||
* This can be used so your application can show <i>something</i> while waiting for the latest data
|
||||
* to be downloaded. To restrict a request to locally-cached resources, add the {@code
|
||||
* only-if-cached} directive: <pre> {@code
|
||||
*
|
||||
* Request request = new Request.Builder()
|
||||
* .cacheControl(new CacheControl.Builder()
|
||||
* .onlyIfCached()
|
||||
* .build())
|
||||
* .url("http://publicobject.com/helloworld.txt")
|
||||
* .build();
|
||||
* Response forceCacheResponse = client.newCall(request).execute();
|
||||
* if (forceCacheResponse.code() != 504) {
|
||||
* // The resource was cached! Show it.
|
||||
* } else {
|
||||
* // The resource was not cached.
|
||||
* }
|
||||
* }</pre>
|
||||
* This technique works even better in situations where a stale response is better than no response.
|
||||
* To permit stale cached responses, use the {@code max-stale} directive with the maximum staleness
|
||||
* in seconds: <pre> {@code
|
||||
*
|
||||
* Request request = new Request.Builder()
|
||||
* .cacheControl(new CacheControl.Builder()
|
||||
* .maxStale(365, TimeUnit.DAYS)
|
||||
* .build())
|
||||
* .url("http://publicobject.com/helloworld.txt")
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>The {@link CacheControl} class can configure request caching directives and parse response
|
||||
* caching directives. It even offers convenient constants {@link CacheControl#FORCE_NETWORK} and
|
||||
* {@link CacheControl#FORCE_CACHE} that address the use cases above.
|
||||
*/
|
||||
public final class Cache implements Closeable, Flushable {
|
||||
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;
|
||||
|
||||
final InternalCache internalCache = new InternalCache() {
|
||||
@Override public Response get(Request request) throws IOException {
|
||||
return Cache.this.get(request);
|
||||
}
|
||||
|
||||
@Override public CacheRequest put(Response response) throws IOException {
|
||||
return Cache.this.put(response);
|
||||
}
|
||||
|
||||
@Override public void remove(Request request) throws IOException {
|
||||
Cache.this.remove(request);
|
||||
}
|
||||
|
||||
@Override public void update(Response cached, Response network) throws IOException {
|
||||
Cache.this.update(cached, network);
|
||||
}
|
||||
|
||||
@Override public void trackConditionalCacheHit() {
|
||||
Cache.this.trackConditionalCacheHit();
|
||||
}
|
||||
|
||||
@Override public void trackResponse(CacheStrategy cacheStrategy) {
|
||||
Cache.this.trackResponse(cacheStrategy);
|
||||
}
|
||||
};
|
||||
|
||||
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 Cache(File directory, long maxSize) {
|
||||
this(directory, maxSize, FileSystem.SYSTEM);
|
||||
}
|
||||
|
||||
Cache(File directory, long maxSize, FileSystem fileSystem) {
|
||||
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
|
||||
}
|
||||
|
||||
private static String urlToKey(Request request) {
|
||||
return Util.md5Hex(request.url().toString());
|
||||
}
|
||||
|
||||
Response get(Request request) {
|
||||
String key = urlToKey(request);
|
||||
DiskLruCache.Snapshot snapshot;
|
||||
Entry entry;
|
||||
try {
|
||||
snapshot = cache.get(key);
|
||||
if (snapshot == null) {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Give up because the cache cannot be read.
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
|
||||
} catch (IOException e) {
|
||||
Util.closeQuietly(snapshot);
|
||||
return null;
|
||||
}
|
||||
|
||||
Response response = entry.response(snapshot);
|
||||
|
||||
if (!entry.matches(request, response)) {
|
||||
Util.closeQuietly(response.body());
|
||||
return null;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private CacheRequest put(Response response) throws IOException {
|
||||
String requestMethod = response.request().method();
|
||||
|
||||
if (HttpMethod.invalidatesCache(response.request().method())) {
|
||||
try {
|
||||
remove(response.request());
|
||||
} catch (IOException ignored) {
|
||||
// The cache cannot be written.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (OkHeaders.hasVaryAll(response)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Entry entry = new Entry(response);
|
||||
DiskLruCache.Editor editor = null;
|
||||
try {
|
||||
editor = cache.edit(urlToKey(response.request()));
|
||||
if (editor == null) {
|
||||
return null;
|
||||
}
|
||||
entry.writeTo(editor);
|
||||
return new CacheRequestImpl(editor);
|
||||
} catch (IOException e) {
|
||||
abortQuietly(editor);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(Request request) throws IOException {
|
||||
cache.remove(urlToKey(request));
|
||||
}
|
||||
|
||||
private void update(Response cached, Response network) {
|
||||
Entry entry = new Entry(network);
|
||||
DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).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) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the cache. This will include reading the journal files from the storage and building
|
||||
* up the necessary in-memory cache information.
|
||||
*
|
||||
* <p>The initialization time may vary depending on the journal file size and the current actual
|
||||
* cache size. The application needs to be aware of calling this function during the
|
||||
* initialization phase and preferably in a background worker thread.
|
||||
*
|
||||
* <p>Note that if the application chooses to not call this method to initialize the cache. By
|
||||
* default, the okhttp will perform lazy initialization upon the first usage of the cache.
|
||||
*/
|
||||
public void initialize() throws IOException {
|
||||
cache.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cache and deletes all of its stored values. This will delete all files in the cache
|
||||
* directory including files that weren't created by the cache.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
cache.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all values stored in the cache. In-flight writes to the cache will complete normally,
|
||||
* but the corresponding responses will not be stored.
|
||||
*/
|
||||
public void evictAll() throws IOException {
|
||||
cache.evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the URLs in this cache. This iterator doesn't throw {@code
|
||||
* ConcurrentModificationException}, but if new responses are added while iterating, their URLs
|
||||
* will not be returned. If existing responses are evicted during iteration, they will be absent
|
||||
* (unless they were already returned).
|
||||
*
|
||||
* <p>The iterator supports {@linkplain Iterator#remove}. Removing a URL from the iterator evicts
|
||||
* the corresponding response from the cache. Use this to evict selected responses.
|
||||
*/
|
||||
public Iterator<String> urls() throws IOException {
|
||||
return new Iterator<String>() {
|
||||
final Iterator<DiskLruCache.Snapshot> delegate = cache.snapshots();
|
||||
|
||||
String nextUrl;
|
||||
boolean canRemove;
|
||||
|
||||
@Override public boolean hasNext() {
|
||||
if (nextUrl != null) return true;
|
||||
|
||||
canRemove = false; // Prevent delegate.remove() on the wrong item!
|
||||
while (delegate.hasNext()) {
|
||||
DiskLruCache.Snapshot snapshot = delegate.next();
|
||||
try {
|
||||
BufferedSource metadata = Okio.buffer(snapshot.getSource(ENTRY_METADATA));
|
||||
nextUrl = metadata.readUtf8LineStrict();
|
||||
return true;
|
||||
} catch (IOException ignored) {
|
||||
// We couldn't read the metadata for this snapshot; possibly because the host filesystem
|
||||
// has disappeared! Skip it.
|
||||
} finally {
|
||||
snapshot.close();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public String next() {
|
||||
if (!hasNext()) throw new NoSuchElementException();
|
||||
String result = nextUrl;
|
||||
nextUrl = null;
|
||||
canRemove = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public void remove() {
|
||||
if (!canRemove) throw new IllegalStateException("remove() before next()");
|
||||
delegate.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public synchronized int writeAbortCount() {
|
||||
return writeAbortCount;
|
||||
}
|
||||
|
||||
public synchronized int writeSuccessCount() {
|
||||
return writeSuccessCount;
|
||||
}
|
||||
|
||||
public long size() throws IOException {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
public long maxSize() {
|
||||
return cache.getMaxSize();
|
||||
}
|
||||
|
||||
@Override public void flush() throws IOException {
|
||||
cache.flush();
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
cache.close();
|
||||
}
|
||||
|
||||
public File directory() {
|
||||
return cache.getDirectory();
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return cache.isClosed();
|
||||
}
|
||||
|
||||
private synchronized void trackResponse(CacheStrategy cacheStrategy) {
|
||||
requestCount++;
|
||||
|
||||
if (cacheStrategy.networkRequest != null) {
|
||||
// If this is a conditional request, we'll increment hitCount if/when it hits.
|
||||
networkCount++;
|
||||
} else if (cacheStrategy.cacheResponse != null) {
|
||||
// This response uses the cache and not the network. That's a cache hit.
|
||||
hitCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void trackConditionalCacheHit() {
|
||||
hitCount++;
|
||||
}
|
||||
|
||||
public synchronized int networkCount() {
|
||||
return networkCount;
|
||||
}
|
||||
|
||||
public synchronized int hitCount() {
|
||||
return hitCount;
|
||||
}
|
||||
|
||||
public synchronized int requestCount() {
|
||||
return requestCount;
|
||||
}
|
||||
|
||||
private final class CacheRequestImpl implements CacheRequest {
|
||||
private final DiskLruCache.Editor editor;
|
||||
private Sink cacheOut;
|
||||
private boolean done;
|
||||
private Sink body;
|
||||
|
||||
public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
|
||||
this.editor = editor;
|
||||
this.cacheOut = editor.newSink(ENTRY_BODY);
|
||||
this.body = new ForwardingSink(cacheOut) {
|
||||
@Override public void close() throws IOException {
|
||||
synchronized (Cache.this) {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
writeSuccessCount++;
|
||||
}
|
||||
super.close();
|
||||
editor.commit();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override public void abort() {
|
||||
synchronized (Cache.this) {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
writeAbortCount++;
|
||||
}
|
||||
Util.closeQuietly(cacheOut);
|
||||
try {
|
||||
editor.abort();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Sink body() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Entry {
|
||||
private final String url;
|
||||
private final Headers varyHeaders;
|
||||
private final String requestMethod;
|
||||
private final Protocol protocol;
|
||||
private final int code;
|
||||
private final String message;
|
||||
private final Headers responseHeaders;
|
||||
private final Handshake handshake;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* TLSv1.2
|
||||
* }</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. The last line is optional. If present, it contains the TLS version.
|
||||
*/
|
||||
public Entry(Source in) throws IOException {
|
||||
try {
|
||||
BufferedSource source = Okio.buffer(in);
|
||||
url = source.readUtf8LineStrict();
|
||||
requestMethod = source.readUtf8LineStrict();
|
||||
Headers.Builder varyHeadersBuilder = new Headers.Builder();
|
||||
int varyRequestHeaderLineCount = readInt(source);
|
||||
for (int i = 0; i < varyRequestHeaderLineCount; i++) {
|
||||
varyHeadersBuilder.addLenient(source.readUtf8LineStrict());
|
||||
}
|
||||
varyHeaders = varyHeadersBuilder.build();
|
||||
|
||||
StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
|
||||
protocol = statusLine.protocol;
|
||||
code = statusLine.code;
|
||||
message = statusLine.message;
|
||||
Headers.Builder responseHeadersBuilder = new Headers.Builder();
|
||||
int responseHeaderLineCount = readInt(source);
|
||||
for (int i = 0; i < responseHeaderLineCount; i++) {
|
||||
responseHeadersBuilder.addLenient(source.readUtf8LineStrict());
|
||||
}
|
||||
responseHeaders = responseHeadersBuilder.build();
|
||||
|
||||
if (isHttps()) {
|
||||
String blank = source.readUtf8LineStrict();
|
||||
if (blank.length() > 0) {
|
||||
throw new IOException("expected \"\" but was \"" + blank + "\"");
|
||||
}
|
||||
String cipherSuiteString = source.readUtf8LineStrict();
|
||||
CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString);
|
||||
List<Certificate> peerCertificates = readCertificateList(source);
|
||||
List<Certificate> localCertificates = readCertificateList(source);
|
||||
TlsVersion tlsVersion = !source.exhausted()
|
||||
? TlsVersion.forJavaName(source.readUtf8LineStrict())
|
||||
: null;
|
||||
handshake = Handshake.get(tlsVersion, cipherSuite, peerCertificates, localCertificates);
|
||||
} else {
|
||||
handshake = null;
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
public Entry(Response response) {
|
||||
this.url = response.request().url().toString();
|
||||
this.varyHeaders = OkHeaders.varyHeaders(response);
|
||||
this.requestMethod = response.request().method();
|
||||
this.protocol = response.protocol();
|
||||
this.code = response.code();
|
||||
this.message = response.message();
|
||||
this.responseHeaders = response.headers();
|
||||
this.handshake = response.handshake();
|
||||
}
|
||||
|
||||
public void writeTo(DiskLruCache.Editor editor) throws IOException {
|
||||
BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
|
||||
|
||||
sink.writeUtf8(url);
|
||||
sink.writeByte('\n');
|
||||
sink.writeUtf8(requestMethod);
|
||||
sink.writeByte('\n');
|
||||
sink.writeDecimalLong(varyHeaders.size());
|
||||
sink.writeByte('\n');
|
||||
for (int i = 0, size = varyHeaders.size(); i < size; i++) {
|
||||
sink.writeUtf8(varyHeaders.name(i));
|
||||
sink.writeUtf8(": ");
|
||||
sink.writeUtf8(varyHeaders.value(i));
|
||||
sink.writeByte('\n');
|
||||
}
|
||||
|
||||
sink.writeUtf8(new StatusLine(protocol, code, message).toString());
|
||||
sink.writeByte('\n');
|
||||
sink.writeDecimalLong(responseHeaders.size());
|
||||
sink.writeByte('\n');
|
||||
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
|
||||
sink.writeUtf8(responseHeaders.name(i));
|
||||
sink.writeUtf8(": ");
|
||||
sink.writeUtf8(responseHeaders.value(i));
|
||||
sink.writeByte('\n');
|
||||
}
|
||||
|
||||
if (isHttps()) {
|
||||
sink.writeByte('\n');
|
||||
sink.writeUtf8(handshake.cipherSuite().javaName());
|
||||
sink.writeByte('\n');
|
||||
writeCertList(sink, handshake.peerCertificates());
|
||||
writeCertList(sink, handshake.localCertificates());
|
||||
// The handshake’s TLS version is null on HttpsURLConnection and on older cached responses.
|
||||
if (handshake.tlsVersion() != null) {
|
||||
sink.writeUtf8(handshake.tlsVersion().javaName());
|
||||
sink.writeByte('\n');
|
||||
}
|
||||
}
|
||||
sink.close();
|
||||
}
|
||||
|
||||
private boolean isHttps() {
|
||||
return url.startsWith("https://");
|
||||
}
|
||||
|
||||
private List<Certificate> readCertificateList(BufferedSource source) throws IOException {
|
||||
int length = readInt(source);
|
||||
if (length == -1) return Collections.emptyList(); // OkHttp v1.2 used -1 to indicate null.
|
||||
|
||||
try {
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
List<Certificate> result = new ArrayList<>(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
String line = source.readUtf8LineStrict();
|
||||
Buffer bytes = new Buffer();
|
||||
bytes.write(ByteString.decodeBase64(line));
|
||||
result.add(certificateFactory.generateCertificate(bytes.inputStream()));
|
||||
}
|
||||
return result;
|
||||
} catch (CertificateException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeCertList(BufferedSink sink, List<Certificate> certificates)
|
||||
throws IOException {
|
||||
try {
|
||||
sink.writeDecimalLong(certificates.size());
|
||||
sink.writeByte('\n');
|
||||
for (int i = 0, size = certificates.size(); i < size; i++) {
|
||||
byte[] bytes = certificates.get(i).getEncoded();
|
||||
String line = ByteString.of(bytes).base64();
|
||||
sink.writeUtf8(line);
|
||||
sink.writeByte('\n');
|
||||
}
|
||||
} catch (CertificateEncodingException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(Request request, Response response) {
|
||||
return url.equals(request.url().toString())
|
||||
&& requestMethod.equals(request.method())
|
||||
&& OkHeaders.varyMatches(response, varyHeaders, request);
|
||||
}
|
||||
|
||||
public Response response(DiskLruCache.Snapshot snapshot) {
|
||||
String contentType = responseHeaders.get("Content-Type");
|
||||
String contentLength = responseHeaders.get("Content-Length");
|
||||
Request cacheRequest = new Request.Builder()
|
||||
.url(url)
|
||||
.method(requestMethod, null)
|
||||
.headers(varyHeaders)
|
||||
.build();
|
||||
return new Response.Builder()
|
||||
.request(cacheRequest)
|
||||
.protocol(protocol)
|
||||
.code(code)
|
||||
.message(message)
|
||||
.headers(responseHeaders)
|
||||
.body(new CacheResponseBody(snapshot, contentType, contentLength))
|
||||
.handshake(handshake)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static int readInt(BufferedSource source) throws IOException {
|
||||
try {
|
||||
long result = source.readDecimalLong();
|
||||
String line = source.readUtf8LineStrict();
|
||||
if (result < 0 || result > Integer.MAX_VALUE || !line.isEmpty()) {
|
||||
throw new IOException("expected an int but was \"" + result + line + "\"");
|
||||
}
|
||||
return (int) result;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static class CacheResponseBody extends ResponseBody {
|
||||
private final DiskLruCache.Snapshot snapshot;
|
||||
private final BufferedSource bodySource;
|
||||
private final String contentType;
|
||||
private final String contentLength;
|
||||
|
||||
public CacheResponseBody(final DiskLruCache.Snapshot snapshot,
|
||||
String contentType, String contentLength) {
|
||||
this.snapshot = snapshot;
|
||||
this.contentType = contentType;
|
||||
this.contentLength = contentLength;
|
||||
|
||||
Source source = snapshot.getSource(ENTRY_BODY);
|
||||
bodySource = Okio.buffer(new ForwardingSource(source) {
|
||||
@Override public void close() throws IOException {
|
||||
snapshot.close();
|
||||
super.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public MediaType contentType() {
|
||||
return contentType != null ? MediaType.parse(contentType) : null;
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
try {
|
||||
return contentLength != null ? Long.parseLong(contentLength) : -1;
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public BufferedSource source() {
|
||||
return bodySource;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
package okhttp3;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import okhttp3.internal.http.HeaderParser;
|
||||
|
||||
/**
|
||||
* A Cache-Control header with cache directives from a server or client. These directives set policy
|
||||
* on what responses can be stored, and which requests can be satisfied by those stored responses.
|
||||
*
|
||||
* <p>See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">RFC 2616,
|
||||
* 14.9</a>.
|
||||
*/
|
||||
public final class CacheControl {
|
||||
/**
|
||||
* Cache control request directives that require network validation of responses. Note that such
|
||||
* requests may be assisted by the cache via conditional GET requests.
|
||||
*/
|
||||
public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
|
||||
|
||||
/**
|
||||
* Cache control request directives that uses the cache only, even if the cached response is
|
||||
* stale. If the response isn't available in the cache or requires server validation, the call
|
||||
* will fail with a {@code 504 Unsatisfiable Request}.
|
||||
*/
|
||||
public static final CacheControl FORCE_CACHE = new Builder()
|
||||
.onlyIfCached()
|
||||
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
private final boolean noCache;
|
||||
private final boolean noStore;
|
||||
private final int maxAgeSeconds;
|
||||
private final int sMaxAgeSeconds;
|
||||
private final boolean isPrivate;
|
||||
private final boolean isPublic;
|
||||
private final boolean mustRevalidate;
|
||||
private final int maxStaleSeconds;
|
||||
private final int minFreshSeconds;
|
||||
private final boolean onlyIfCached;
|
||||
private final boolean noTransform;
|
||||
|
||||
String headerValue; // Lazily computed, null if absent.
|
||||
|
||||
private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds,
|
||||
boolean isPrivate, boolean isPublic, boolean mustRevalidate, int maxStaleSeconds,
|
||||
int minFreshSeconds, boolean onlyIfCached, boolean noTransform, String headerValue) {
|
||||
this.noCache = noCache;
|
||||
this.noStore = noStore;
|
||||
this.maxAgeSeconds = maxAgeSeconds;
|
||||
this.sMaxAgeSeconds = sMaxAgeSeconds;
|
||||
this.isPrivate = isPrivate;
|
||||
this.isPublic = isPublic;
|
||||
this.mustRevalidate = mustRevalidate;
|
||||
this.maxStaleSeconds = maxStaleSeconds;
|
||||
this.minFreshSeconds = minFreshSeconds;
|
||||
this.onlyIfCached = onlyIfCached;
|
||||
this.noTransform = noTransform;
|
||||
this.headerValue = headerValue;
|
||||
}
|
||||
|
||||
private CacheControl(Builder builder) {
|
||||
this.noCache = builder.noCache;
|
||||
this.noStore = builder.noStore;
|
||||
this.maxAgeSeconds = builder.maxAgeSeconds;
|
||||
this.sMaxAgeSeconds = -1;
|
||||
this.isPrivate = false;
|
||||
this.isPublic = false;
|
||||
this.mustRevalidate = false;
|
||||
this.maxStaleSeconds = builder.maxStaleSeconds;
|
||||
this.minFreshSeconds = builder.minFreshSeconds;
|
||||
this.onlyIfCached = builder.onlyIfCached;
|
||||
this.noTransform = builder.noTransform;
|
||||
}
|
||||
|
||||
/**
|
||||
* In a response, this field's name "no-cache" is misleading. It doesn't prevent us from caching
|
||||
* the response; it only means we have to validate the response with the origin server before
|
||||
* returning it. We can do this with a conditional GET.
|
||||
*
|
||||
* <p>In a request, it means do not use a cache to satisfy the request.
|
||||
*/
|
||||
public boolean noCache() {
|
||||
return noCache;
|
||||
}
|
||||
|
||||
/** If true, this response should not be cached. */
|
||||
public boolean noStore() {
|
||||
return noStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* The duration past the response's served date that it can be served without validation.
|
||||
*/
|
||||
public int maxAgeSeconds() {
|
||||
return maxAgeSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "s-maxage" directive is the max age for shared caches. Not to be confused with "max-age"
|
||||
* for non-shared caches, As in Firefox and Chrome, this directive is not honored by this cache.
|
||||
*/
|
||||
public int sMaxAgeSeconds() {
|
||||
return sMaxAgeSeconds;
|
||||
}
|
||||
|
||||
public boolean isPrivate() {
|
||||
return isPrivate;
|
||||
}
|
||||
|
||||
public boolean isPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
|
||||
public boolean mustRevalidate() {
|
||||
return mustRevalidate;
|
||||
}
|
||||
|
||||
public int maxStaleSeconds() {
|
||||
return maxStaleSeconds;
|
||||
}
|
||||
|
||||
public int minFreshSeconds() {
|
||||
return minFreshSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* This field's name "only-if-cached" is misleading. It actually means "do not use the network".
|
||||
* It is set by a client who only wants to make a request if it can be fully satisfied by the
|
||||
* cache. Cached responses that would require validation (ie. conditional gets) are not permitted
|
||||
* if this header is set.
|
||||
*/
|
||||
public boolean onlyIfCached() {
|
||||
return onlyIfCached;
|
||||
}
|
||||
|
||||
public boolean noTransform() {
|
||||
return noTransform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache directives of {@code headers}. This honors both Cache-Control and Pragma
|
||||
* headers if they are present.
|
||||
*/
|
||||
public static CacheControl parse(Headers headers) {
|
||||
boolean noCache = false;
|
||||
boolean noStore = false;
|
||||
int maxAgeSeconds = -1;
|
||||
int sMaxAgeSeconds = -1;
|
||||
boolean isPrivate = false;
|
||||
boolean isPublic = false;
|
||||
boolean mustRevalidate = false;
|
||||
int maxStaleSeconds = -1;
|
||||
int minFreshSeconds = -1;
|
||||
boolean onlyIfCached = false;
|
||||
boolean noTransform = false;
|
||||
|
||||
boolean canUseHeaderValue = true;
|
||||
String headerValue = null;
|
||||
|
||||
for (int i = 0, size = headers.size(); i < size; i++) {
|
||||
String name = headers.name(i);
|
||||
String value = headers.value(i);
|
||||
|
||||
if (name.equalsIgnoreCase("Cache-Control")) {
|
||||
if (headerValue != null) {
|
||||
// Multiple cache-control headers means we can't use the raw value.
|
||||
canUseHeaderValue = false;
|
||||
} else {
|
||||
headerValue = value;
|
||||
}
|
||||
} else if (name.equalsIgnoreCase("Pragma")) {
|
||||
// Might specify additional cache-control params. We invalidate just in case.
|
||||
canUseHeaderValue = false;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
while (pos < value.length()) {
|
||||
int tokenStart = pos;
|
||||
pos = HeaderParser.skipUntil(value, pos, "=,;");
|
||||
String directive = value.substring(tokenStart, pos).trim();
|
||||
String parameter;
|
||||
|
||||
if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
|
||||
pos++; // consume ',' or ';' (if necessary)
|
||||
parameter = null;
|
||||
} else {
|
||||
pos++; // consume '='
|
||||
pos = HeaderParser.skipWhitespace(value, pos);
|
||||
|
||||
// quoted string
|
||||
if (pos < value.length() && value.charAt(pos) == '\"') {
|
||||
pos++; // consume '"' open quote
|
||||
int parameterStart = pos;
|
||||
pos = HeaderParser.skipUntil(value, pos, "\"");
|
||||
parameter = value.substring(parameterStart, pos);
|
||||
pos++; // consume '"' close quote (if necessary)
|
||||
|
||||
// unquoted string
|
||||
} else {
|
||||
int parameterStart = pos;
|
||||
pos = HeaderParser.skipUntil(value, pos, ",;");
|
||||
parameter = value.substring(parameterStart, pos).trim();
|
||||
}
|
||||
}
|
||||
|
||||
if ("no-cache".equalsIgnoreCase(directive)) {
|
||||
noCache = true;
|
||||
} else if ("no-store".equalsIgnoreCase(directive)) {
|
||||
noStore = true;
|
||||
} else if ("max-age".equalsIgnoreCase(directive)) {
|
||||
maxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
|
||||
} else if ("s-maxage".equalsIgnoreCase(directive)) {
|
||||
sMaxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
|
||||
} else if ("private".equalsIgnoreCase(directive)) {
|
||||
isPrivate = true;
|
||||
} else if ("public".equalsIgnoreCase(directive)) {
|
||||
isPublic = true;
|
||||
} else if ("must-revalidate".equalsIgnoreCase(directive)) {
|
||||
mustRevalidate = true;
|
||||
} else if ("max-stale".equalsIgnoreCase(directive)) {
|
||||
maxStaleSeconds = HeaderParser.parseSeconds(parameter, Integer.MAX_VALUE);
|
||||
} else if ("min-fresh".equalsIgnoreCase(directive)) {
|
||||
minFreshSeconds = HeaderParser.parseSeconds(parameter, -1);
|
||||
} else if ("only-if-cached".equalsIgnoreCase(directive)) {
|
||||
onlyIfCached = true;
|
||||
} else if ("no-transform".equalsIgnoreCase(directive)) {
|
||||
noTransform = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!canUseHeaderValue) {
|
||||
headerValue = null;
|
||||
}
|
||||
return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic,
|
||||
mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, headerValue);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
String result = headerValue;
|
||||
return result != null ? result : (headerValue = headerValue());
|
||||
}
|
||||
|
||||
private String headerValue() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (noCache) result.append("no-cache, ");
|
||||
if (noStore) result.append("no-store, ");
|
||||
if (maxAgeSeconds != -1) result.append("max-age=").append(maxAgeSeconds).append(", ");
|
||||
if (sMaxAgeSeconds != -1) result.append("s-maxage=").append(sMaxAgeSeconds).append(", ");
|
||||
if (isPrivate) result.append("private, ");
|
||||
if (isPublic) result.append("public, ");
|
||||
if (mustRevalidate) result.append("must-revalidate, ");
|
||||
if (maxStaleSeconds != -1) result.append("max-stale=").append(maxStaleSeconds).append(", ");
|
||||
if (minFreshSeconds != -1) result.append("min-fresh=").append(minFreshSeconds).append(", ");
|
||||
if (onlyIfCached) result.append("only-if-cached, ");
|
||||
if (noTransform) result.append("no-transform, ");
|
||||
if (result.length() == 0) return "";
|
||||
result.delete(result.length() - 2, result.length());
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/** Builds a {@code Cache-Control} request header. */
|
||||
public static final class Builder {
|
||||
boolean noCache;
|
||||
boolean noStore;
|
||||
int maxAgeSeconds = -1;
|
||||
int maxStaleSeconds = -1;
|
||||
int minFreshSeconds = -1;
|
||||
boolean onlyIfCached;
|
||||
boolean noTransform;
|
||||
|
||||
/** Don't accept an unvalidated cached response. */
|
||||
public Builder noCache() {
|
||||
this.noCache = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Don't store the server's response in any cache. */
|
||||
public Builder noStore() {
|
||||
this.noStore = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum age of a cached response. If the cache response's age exceeds {@code
|
||||
* maxAge}, it will not be used and a network request will be made.
|
||||
*
|
||||
* @param maxAge a non-negative integer. This is stored and transmitted with {@link
|
||||
* TimeUnit#SECONDS} precision; finer precision will be lost.
|
||||
*/
|
||||
public Builder maxAge(int maxAge, TimeUnit timeUnit) {
|
||||
if (maxAge < 0) throw new IllegalArgumentException("maxAge < 0: " + maxAge);
|
||||
long maxAgeSecondsLong = timeUnit.toSeconds(maxAge);
|
||||
this.maxAgeSeconds = maxAgeSecondsLong > Integer.MAX_VALUE
|
||||
? Integer.MAX_VALUE
|
||||
: (int) maxAgeSecondsLong;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept cached responses that have exceeded their freshness lifetime by up to {@code
|
||||
* maxStale}. If unspecified, stale cache responses will not be used.
|
||||
*
|
||||
* @param maxStale a non-negative integer. This is stored and transmitted with {@link
|
||||
* TimeUnit#SECONDS} precision; finer precision will be lost.
|
||||
*/
|
||||
public Builder maxStale(int maxStale, TimeUnit timeUnit) {
|
||||
if (maxStale < 0) throw new IllegalArgumentException("maxStale < 0: " + maxStale);
|
||||
long maxStaleSecondsLong = timeUnit.toSeconds(maxStale);
|
||||
this.maxStaleSeconds = maxStaleSecondsLong > Integer.MAX_VALUE
|
||||
? Integer.MAX_VALUE
|
||||
: (int) maxStaleSecondsLong;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of seconds that a response will continue to be fresh for. If the
|
||||
* response will be stale when {@code minFresh} have elapsed, the cached response will not be
|
||||
* used and a network request will be made.
|
||||
*
|
||||
* @param minFresh a non-negative integer. This is stored and transmitted with {@link
|
||||
* TimeUnit#SECONDS} precision; finer precision will be lost.
|
||||
*/
|
||||
public Builder minFresh(int minFresh, TimeUnit timeUnit) {
|
||||
if (minFresh < 0) throw new IllegalArgumentException("minFresh < 0: " + minFresh);
|
||||
long minFreshSecondsLong = timeUnit.toSeconds(minFresh);
|
||||
this.minFreshSeconds = minFreshSecondsLong > Integer.MAX_VALUE
|
||||
? Integer.MAX_VALUE
|
||||
: (int) minFreshSecondsLong;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only accept the response if it is in the cache. If the response isn't cached, a {@code 504
|
||||
* Unsatisfiable Request} response will be returned.
|
||||
*/
|
||||
public Builder onlyIfCached() {
|
||||
this.onlyIfCached = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Don't accept a transformed response. */
|
||||
public Builder noTransform() {
|
||||
this.noTransform = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CacheControl build() {
|
||||
return new CacheControl(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A call is a request that has been prepared for execution. A call can be canceled. As this object
|
||||
* represents a single request/response pair (stream), it cannot be executed twice.
|
||||
*/
|
||||
public interface Call {
|
||||
/** Returns the original request that initiated this call. */
|
||||
Request request();
|
||||
|
||||
/**
|
||||
* Invokes the request immediately, and blocks until the response can be processed or is in
|
||||
* error.
|
||||
*
|
||||
* <p>The caller may read the response body with the response's {@link Response#body} method. To
|
||||
* facilitate connection recycling, callers should always {@link ResponseBody#close() close the
|
||||
* response body}.
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* @throws IOException if the request could not be executed due to cancellation, 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.
|
||||
* @throws IllegalStateException when the call has already been executed.
|
||||
*/
|
||||
Response execute() throws IOException;
|
||||
|
||||
/**
|
||||
* Schedules the request to be executed at some point in the future.
|
||||
*
|
||||
* <p>The {@link OkHttpClient#dispatcher dispatcher} defines when the request will run: usually
|
||||
* immediately unless there are several other requests currently being executed.
|
||||
*
|
||||
* <p>This client will later call back {@code responseCallback} with either an HTTP response or a
|
||||
* failure exception.
|
||||
*
|
||||
* @throws IllegalStateException when the call has already been executed.
|
||||
*/
|
||||
void enqueue(Callback responseCallback);
|
||||
|
||||
/** Cancels the request, if possible. Requests that are already complete cannot be canceled. */
|
||||
void cancel();
|
||||
|
||||
/**
|
||||
* Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
|
||||
* #enqueue(Callback) enqueued}. It is an error to execute a call more than once.
|
||||
*/
|
||||
boolean isExecuted();
|
||||
|
||||
boolean isCanceled();
|
||||
|
||||
interface Factory {
|
||||
Call newCall(Request request);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface Callback {
|
||||
/**
|
||||
* Called when the request could not be executed due to cancellation, 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(Call call, IOException e);
|
||||
|
||||
/**
|
||||
* Called when the HTTP response was successfully returned by the remote server. The callback may
|
||||
* proceed to read the response body with {@link Response#body}. The response is still live until
|
||||
* its response body is closed with {@code response.body().close()}. The recipient of the callback
|
||||
* may even consume the response body on another thread.
|
||||
*
|
||||
* <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.
|
||||
*/
|
||||
void onResponse(Call call, Response response) throws IOException;
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import okhttp3.internal.Util;
|
||||
import okhttp3.internal.tls.CertificateChainCleaner;
|
||||
import okhttp3.internal.tls.TrustRootIndex;
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* Constrains which certificates are trusted. Pinning certificates defends against attacks on
|
||||
* certificate authorities. It also prevents connections through man-in-the-middle certificate
|
||||
* authorities either known or unknown to the application's user.
|
||||
*
|
||||
* <p>This class currently pins a certificate's Subject Public Key Info as described on <a
|
||||
* href="http://goo.gl/AIx3e5">Adam Langley's Weblog</a>. Pins are either base64 SHA-256 hashes as
|
||||
* in <a href="http://tools.ietf.org/html/rfc7469">HTTP Public Key Pinning (HPKP)</a> or SHA-1
|
||||
* base64 hashes as in Chromium's <a href="http://goo.gl/XDh6je">static certificates</a>.
|
||||
*
|
||||
* <h3>Setting up Certificate Pinning</h3>
|
||||
*
|
||||
* <p>The easiest way to pin a host is turn on pinning with a broken configuration and read the
|
||||
* expected configuration when the connection fails. Be sure to do this on a trusted network, and
|
||||
* without man-in-the-middle tools like <a href="http://charlesproxy.com">Charles</a> or <a
|
||||
* href="http://fiddlertool.com">Fiddler</a>.
|
||||
*
|
||||
* <p>For example, to pin {@code https://publicobject.com}, start with a broken
|
||||
* configuration: <pre> {@code
|
||||
*
|
||||
* String hostname = "publicobject.com";
|
||||
* CertificatePinner certificatePinner = new CertificatePinner.Builder()
|
||||
* .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
|
||||
* .build();
|
||||
* OkHttpClient client = new OkHttpClient();
|
||||
* client.setCertificatePinner(certificatePinner);
|
||||
*
|
||||
* Request request = new Request.Builder()
|
||||
* .url("https://" + hostname)
|
||||
* .build();
|
||||
* client.newCall(request).execute();
|
||||
* }</pre>
|
||||
*
|
||||
* As expected, this fails with a certificate pinning exception: <pre> {@code
|
||||
*
|
||||
* javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
|
||||
* Peer certificate chain:
|
||||
* sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=publicobject.com, OU=PositiveSSL
|
||||
* sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA
|
||||
* sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority
|
||||
* sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root
|
||||
* Pinned certificates for publicobject.com:
|
||||
* sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
* at okhttp3.CertificatePinner.check(CertificatePinner.java)
|
||||
* at okhttp3.Connection.upgradeToTls(Connection.java)
|
||||
* at okhttp3.Connection.connect(Connection.java)
|
||||
* at okhttp3.Connection.connectAndSetOwner(Connection.java)
|
||||
* }</pre>
|
||||
*
|
||||
* Follow up by pasting the public key hashes from the exception into the
|
||||
* certificate pinner's configuration: <pre> {@code
|
||||
*
|
||||
* CertificatePinner certificatePinner = new CertificatePinner.Builder()
|
||||
* .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
|
||||
* .add("publicobject.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")
|
||||
* .add("publicobject.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")
|
||||
* .add("publicobject.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=")
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
* Pinning is per-hostname and/or per-wildcard pattern. To pin both {@code publicobject.com} and
|
||||
* {@code www.publicobject.com}, you must configure both hostnames.
|
||||
*
|
||||
* <p>Wildcard pattern rules:
|
||||
* <ol>
|
||||
* <li>Asterisk {@code *} is only permitted in the left-most domain name label and must be the
|
||||
* only character in that label (i.e., must match the whole left-most label). For example,
|
||||
* {@code *.example.com} is permitted, while {@code *a.example.com}, {@code a*.example.com},
|
||||
* {@code a*b.example.com}, {@code a.*.example.com} are not permitted.
|
||||
* <li>Asterisk {@code *} cannot match across domain name labels. For example,
|
||||
* {@code *.example.com} matches {@code test.example.com} but does not match
|
||||
* {@code sub.test.example.com}.
|
||||
* <li>Wildcard patterns for single-label domain names are not permitted.
|
||||
* </ol>
|
||||
*
|
||||
* If hostname pinned directly and via wildcard pattern, both direct and wildcard pins will be used.
|
||||
* For example: {@code *.example.com} pinned with {@code pin1} and {@code a.example.com} pinned with
|
||||
* {@code pin2}, to check {@code a.example.com} both {@code pin1} and {@code pin2} will be used.
|
||||
*
|
||||
* <h3>Warning: Certificate Pinning is Dangerous!</h3>
|
||||
*
|
||||
* <p>Pinning certificates limits your server team's abilities to update their TLS certificates. By
|
||||
* pinning certificates, you take on additional operational complexity and limit your ability to
|
||||
* migrate between certificate authorities. Do not use certificate pinning without the blessing of
|
||||
* your server's TLS administrator!
|
||||
*
|
||||
* <h4>Note about self-signed certificates</h4>
|
||||
*
|
||||
* <p>{@link CertificatePinner} can not be used to pin self-signed certificate if such certificate
|
||||
* is not accepted by {@link javax.net.ssl.TrustManager}.
|
||||
*
|
||||
* @see <a href="https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning"> OWASP:
|
||||
* Certificate and Public Key Pinning</a>
|
||||
*/
|
||||
public final class CertificatePinner {
|
||||
public static final CertificatePinner DEFAULT = new Builder().build();
|
||||
|
||||
private final List<Pin> pins;
|
||||
private final TrustRootIndex trustRootIndex;
|
||||
|
||||
private CertificatePinner(Builder builder) {
|
||||
this.pins = Util.immutableList(builder.pins);
|
||||
this.trustRootIndex = builder.trustRootIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that at least one of the certificates pinned for {@code hostname} is in {@code
|
||||
* peerCertificates}. Does nothing if there are no certificates pinned for {@code hostname}.
|
||||
* OkHttp calls this after a successful TLS handshake, but before the connection is used.
|
||||
*
|
||||
* @throws SSLPeerUnverifiedException if {@code peerCertificates} don't match the certificates
|
||||
* pinned for {@code hostname}.
|
||||
*/
|
||||
public void check(String hostname, List<Certificate> peerCertificates)
|
||||
throws SSLPeerUnverifiedException {
|
||||
List<Pin> pins = findMatchingPins(hostname);
|
||||
if (pins.isEmpty()) return;
|
||||
|
||||
if (trustRootIndex != null) {
|
||||
peerCertificates = new CertificateChainCleaner(trustRootIndex).clean(peerCertificates);
|
||||
}
|
||||
|
||||
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
|
||||
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
|
||||
|
||||
// Lazily compute the hashes for each certificate.
|
||||
ByteString sha1 = null;
|
||||
ByteString sha256 = null;
|
||||
|
||||
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
|
||||
Pin pin = pins.get(p);
|
||||
if (pin.hashAlgorithm.equals("sha256/")) {
|
||||
if (sha256 == null) sha256 = sha256(x509Certificate);
|
||||
if (pin.hash.equals(sha256)) return; // Success!
|
||||
} else if (pin.hashAlgorithm.equals("sha1/")) {
|
||||
if (sha1 == null) sha1 = sha1(x509Certificate);
|
||||
if (pin.hash.equals(sha1)) return; // Success!
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't find a matching pin, format a nice exception.
|
||||
StringBuilder message = new StringBuilder()
|
||||
.append("Certificate pinning failure!")
|
||||
.append("\n Peer certificate chain:");
|
||||
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
|
||||
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
|
||||
message.append("\n ").append(pin(x509Certificate))
|
||||
.append(": ").append(x509Certificate.getSubjectDN().getName());
|
||||
}
|
||||
message.append("\n Pinned certificates for ").append(hostname).append(":");
|
||||
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
|
||||
Pin pin = pins.get(p);
|
||||
message.append("\n ").append(pin);
|
||||
}
|
||||
throw new SSLPeerUnverifiedException(message.toString());
|
||||
}
|
||||
|
||||
/** @deprecated replaced with {@link #check(String, List)}. */
|
||||
public void check(String hostname, Certificate... peerCertificates)
|
||||
throws SSLPeerUnverifiedException {
|
||||
check(hostname, Arrays.asList(peerCertificates));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of matching certificates' pins for the hostname. Returns an empty list if the
|
||||
* hostname does not have pinned certificates.
|
||||
*/
|
||||
List<Pin> findMatchingPins(String hostname) {
|
||||
List<Pin> result = Collections.emptyList();
|
||||
for (Pin pin : pins) {
|
||||
if (pin.matches(hostname)) {
|
||||
if (result.isEmpty()) result = new ArrayList<>();
|
||||
result.add(pin);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Builder newBuilder() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SHA-256 of {@code certificate}'s public key.
|
||||
*
|
||||
* <p>In OkHttp 3.1.2 and earlier, this returned a SHA-1 hash of the public key. Both types are
|
||||
* supported, but SHA-256 is preferred.
|
||||
*/
|
||||
public static String pin(Certificate certificate) {
|
||||
if (!(certificate instanceof X509Certificate)) {
|
||||
throw new IllegalArgumentException("Certificate pinning requires X509 certificates");
|
||||
}
|
||||
return "sha256/" + sha256((X509Certificate) certificate).base64();
|
||||
}
|
||||
|
||||
static ByteString sha1(X509Certificate x509Certificate) {
|
||||
return Util.sha1(ByteString.of(x509Certificate.getPublicKey().getEncoded()));
|
||||
}
|
||||
|
||||
static ByteString sha256(X509Certificate x509Certificate) {
|
||||
return Util.sha256(ByteString.of(x509Certificate.getPublicKey().getEncoded()));
|
||||
}
|
||||
|
||||
static final class Pin {
|
||||
/** A hostname like {@code example.com} or a pattern like {@code *.example.com}. */
|
||||
final String pattern;
|
||||
/** Either {@code sha1/} or {@code sha256/}. */
|
||||
final String hashAlgorithm;
|
||||
/** The hash of the pinned certificate using {@link #hashAlgorithm}. */
|
||||
final ByteString hash;
|
||||
|
||||
Pin(String pattern, String pin) {
|
||||
this.pattern = pattern;
|
||||
if (pin.startsWith("sha1/")) {
|
||||
this.hashAlgorithm = "sha1/";
|
||||
this.hash = ByteString.decodeBase64(pin.substring("sha1/".length()));
|
||||
} else if (pin.startsWith("sha256/")) {
|
||||
this.hashAlgorithm = "sha256/";
|
||||
this.hash = ByteString.decodeBase64(pin.substring("sha256/".length()));
|
||||
} else {
|
||||
throw new IllegalArgumentException("pins must start with 'sha256/' or 'sha1/': " + pin);
|
||||
}
|
||||
|
||||
if (this.hash == null) {
|
||||
throw new IllegalArgumentException("pins must be base64: " + pin);
|
||||
}
|
||||
}
|
||||
|
||||
boolean matches(String hostname) {
|
||||
if (pattern.equals(hostname)) return true;
|
||||
|
||||
int firstDot = hostname.indexOf('.');
|
||||
return pattern.startsWith("*.")
|
||||
&& hostname.regionMatches(false, firstDot + 1, pattern, 2, pattern.length() - 2);
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
return other instanceof Pin
|
||||
&& pattern.equals(((Pin) other).pattern)
|
||||
&& hashAlgorithm.equals(((Pin) other).hashAlgorithm)
|
||||
&& hash.equals(((Pin) other).hash);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + pattern.hashCode();
|
||||
result = 31 * result + hashAlgorithm.hashCode();
|
||||
result = 31 * result + hash.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return hashAlgorithm + hash.base64();
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds a configured certificate pinner. */
|
||||
public static final class Builder {
|
||||
private final List<Pin> pins = new ArrayList<>();
|
||||
private TrustRootIndex trustRootIndex;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
Builder(CertificatePinner certificatePinner) {
|
||||
this.pins.addAll(certificatePinner.pins);
|
||||
this.trustRootIndex = certificatePinner.trustRootIndex;
|
||||
}
|
||||
|
||||
public Builder trustRootIndex(TrustRootIndex trustRootIndex) {
|
||||
this.trustRootIndex = trustRootIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins certificates for {@code pattern}.
|
||||
*
|
||||
* @param pattern lower-case host name or wildcard pattern such as {@code *.example.com}.
|
||||
* @param pins SHA-256 or SHA-1 hashes. Each pin is a hash of a certificate's Subject Public Key
|
||||
* Info, base64-encoded and prefixed with either {@code sha256/} or {@code sha1/}.
|
||||
*/
|
||||
public Builder add(String pattern, String... pins) {
|
||||
if (pattern == null) throw new IllegalArgumentException("pattern == null");
|
||||
|
||||
for (String pin : pins) {
|
||||
this.pins.add(new Pin(pattern, pin));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CertificatePinner build() {
|
||||
return new CertificatePinner(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import okhttp3.internal.Util;
|
||||
|
||||
/** 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 scheme() {
|
||||
return scheme;
|
||||
}
|
||||
|
||||
/** Returns the protection space. */
|
||||
public String realm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
return o instanceof Challenge
|
||||
&& Util.equal(scheme, ((Challenge) o).scheme)
|
||||
&& Util.equal(realm, ((Challenge) o).realm);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 29;
|
||||
result = 31 * result + (realm != null ? realm.hashCode() : 0);
|
||||
result = 31 * result + (scheme != null ? scheme.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return scheme + " realm=\"" + realm + "\"";
|
||||
}
|
||||
}
|
||||
@@ -1,384 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import static java.lang.Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* <a href="https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml">TLS cipher
|
||||
* suites</a>.
|
||||
*
|
||||
* <p><strong>Not all cipher suites are supported on all platforms.</strong> As newer cipher suites
|
||||
* are created (for stronger privacy, better performance, etc.) they will be adopted by the platform
|
||||
* and then exposed here. Cipher suites that are not available on either Android (through API level
|
||||
* 20) or Java (through JDK 8) are omitted for brevity.
|
||||
*
|
||||
* <p>See also <a href="https://android.googlesource.com/platform/external/conscrypt/+/master/src/main/java/org/conscrypt/NativeCrypto.java">NativeCrypto.java</a>
|
||||
* from conscrypt, which lists the cipher suites supported by Android.
|
||||
*/
|
||||
public enum CipherSuite {
|
||||
// Last updated 2014-11-11 using cipher suites from Android 21 and Java 8.
|
||||
|
||||
// TLS_NULL_WITH_NULL_NULL("TLS_NULL_WITH_NULL_NULL", 0x0000, 5246, MAX_VALUE, MAX_VALUE),
|
||||
TLS_RSA_WITH_NULL_MD5("SSL_RSA_WITH_NULL_MD5", 0x0001, 5246, 6, 10),
|
||||
TLS_RSA_WITH_NULL_SHA("SSL_RSA_WITH_NULL_SHA", 0x0002, 5246, 6, 10),
|
||||
TLS_RSA_EXPORT_WITH_RC4_40_MD5("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0003, 4346, 6, 10),
|
||||
TLS_RSA_WITH_RC4_128_MD5("SSL_RSA_WITH_RC4_128_MD5", 0x0004, 5246, 6, 10),
|
||||
TLS_RSA_WITH_RC4_128_SHA("SSL_RSA_WITH_RC4_128_SHA", 0x0005, 5246, 6, 10),
|
||||
// TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5("SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", 0x0006, 4346, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_IDEA_CBC_SHA("TLS_RSA_WITH_IDEA_CBC_SHA", 0x0007, 5469, MAX_VALUE, MAX_VALUE),
|
||||
TLS_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0008, 4346, 6, 10),
|
||||
TLS_RSA_WITH_DES_CBC_SHA("SSL_RSA_WITH_DES_CBC_SHA", 0x0009, 5469, 6, 10),
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA("SSL_RSA_WITH_3DES_EDE_CBC_SHA", 0x000a, 5246, 6, 10),
|
||||
// TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x000b, 4346, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_DES_CBC_SHA("TLS_DH_DSS_WITH_DES_CBC_SHA", 0x000c, 5469, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", 0x000d, 5246, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x000e, 4346, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_DES_CBC_SHA("TLS_DH_RSA_WITH_DES_CBC_SHA", 0x000f, 5469, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", 0x0010, 5246, MAX_VALUE, MAX_VALUE),
|
||||
TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x0011, 4346, 6, 10),
|
||||
TLS_DHE_DSS_WITH_DES_CBC_SHA("SSL_DHE_DSS_WITH_DES_CBC_SHA", 0x0012, 5469, 6, 10),
|
||||
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA("SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", 0x0013, 5246, 6, 10),
|
||||
TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0014, 4346, 6, 10),
|
||||
TLS_DHE_RSA_WITH_DES_CBC_SHA("SSL_DHE_RSA_WITH_DES_CBC_SHA", 0x0015, 5469, 6, 10),
|
||||
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", 0x0016, 5246, 6, 10),
|
||||
TLS_DH_anon_EXPORT_WITH_RC4_40_MD5("SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", 0x0017, 4346, 6, 10),
|
||||
TLS_DH_anon_WITH_RC4_128_MD5("SSL_DH_anon_WITH_RC4_128_MD5", 0x0018, 5246, 6, 10),
|
||||
TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", 0x0019, 4346, 6, 10),
|
||||
TLS_DH_anon_WITH_DES_CBC_SHA("SSL_DH_anon_WITH_DES_CBC_SHA", 0x001a, 5469, 6, 10),
|
||||
TLS_DH_anon_WITH_3DES_EDE_CBC_SHA("SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", 0x001b, 5246, 6, 10),
|
||||
TLS_KRB5_WITH_DES_CBC_SHA("TLS_KRB5_WITH_DES_CBC_SHA", 0x001e, 2712, 6, MAX_VALUE),
|
||||
TLS_KRB5_WITH_3DES_EDE_CBC_SHA("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", 0x001f, 2712, 6, MAX_VALUE),
|
||||
TLS_KRB5_WITH_RC4_128_SHA("TLS_KRB5_WITH_RC4_128_SHA", 0x0020, 2712, 6, MAX_VALUE),
|
||||
// TLS_KRB5_WITH_IDEA_CBC_SHA("TLS_KRB5_WITH_IDEA_CBC_SHA", 0x0021, 2712, MAX_VALUE, MAX_VALUE),
|
||||
TLS_KRB5_WITH_DES_CBC_MD5("TLS_KRB5_WITH_DES_CBC_MD5", 0x0022, 2712, 6, MAX_VALUE),
|
||||
TLS_KRB5_WITH_3DES_EDE_CBC_MD5("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", 0x0023, 2712, 6, MAX_VALUE),
|
||||
TLS_KRB5_WITH_RC4_128_MD5("TLS_KRB5_WITH_RC4_128_MD5", 0x0024, 2712, 6, MAX_VALUE),
|
||||
// TLS_KRB5_WITH_IDEA_CBC_MD5("TLS_KRB5_WITH_IDEA_CBC_MD5", 0x0025, 2712, MAX_VALUE, MAX_VALUE),
|
||||
TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", 0x0026, 2712, 6, MAX_VALUE),
|
||||
// TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", 0x0027, 2712, MAX_VALUE, MAX_VALUE),
|
||||
TLS_KRB5_EXPORT_WITH_RC4_40_SHA("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", 0x0028, 2712, 6, MAX_VALUE),
|
||||
TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", 0x0029, 2712, 6, MAX_VALUE),
|
||||
// TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", 0x002a, 2712, MAX_VALUE, MAX_VALUE),
|
||||
TLS_KRB5_EXPORT_WITH_RC4_40_MD5("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", 0x002b, 2712, 6, MAX_VALUE),
|
||||
// TLS_PSK_WITH_NULL_SHA("TLS_PSK_WITH_NULL_SHA", 0x002c, 4785, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_NULL_SHA("TLS_DHE_PSK_WITH_NULL_SHA", 0x002d, 4785, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_NULL_SHA("TLS_RSA_PSK_WITH_NULL_SHA", 0x002e, 4785, MAX_VALUE, MAX_VALUE),
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA("TLS_RSA_WITH_AES_128_CBC_SHA", 0x002f, 5246, 6, 10),
|
||||
// TLS_DH_DSS_WITH_AES_128_CBC_SHA("TLS_DH_DSS_WITH_AES_128_CBC_SHA", 0x0030, 5246, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_AES_128_CBC_SHA("TLS_DH_RSA_WITH_AES_128_CBC_SHA", 0x0031, 5246, MAX_VALUE, MAX_VALUE),
|
||||
TLS_DHE_DSS_WITH_AES_128_CBC_SHA("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", 0x0032, 5246, 6, 10),
|
||||
TLS_DHE_RSA_WITH_AES_128_CBC_SHA("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", 0x0033, 5246, 6, 10),
|
||||
TLS_DH_anon_WITH_AES_128_CBC_SHA("TLS_DH_anon_WITH_AES_128_CBC_SHA", 0x0034, 5246, 6, 10),
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA("TLS_RSA_WITH_AES_256_CBC_SHA", 0x0035, 5246, 6, 10),
|
||||
// TLS_DH_DSS_WITH_AES_256_CBC_SHA("TLS_DH_DSS_WITH_AES_256_CBC_SHA", 0x0036, 5246, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_AES_256_CBC_SHA("TLS_DH_RSA_WITH_AES_256_CBC_SHA", 0x0037, 5246, MAX_VALUE, MAX_VALUE),
|
||||
TLS_DHE_DSS_WITH_AES_256_CBC_SHA("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", 0x0038, 5246, 6, 10),
|
||||
TLS_DHE_RSA_WITH_AES_256_CBC_SHA("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", 0x0039, 5246, 6, 10),
|
||||
TLS_DH_anon_WITH_AES_256_CBC_SHA("TLS_DH_anon_WITH_AES_256_CBC_SHA", 0x003a, 5246, 6, 10),
|
||||
TLS_RSA_WITH_NULL_SHA256("TLS_RSA_WITH_NULL_SHA256", 0x003b, 5246, 7, 21),
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256("TLS_RSA_WITH_AES_128_CBC_SHA256", 0x003c, 5246, 7, 21),
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA256("TLS_RSA_WITH_AES_256_CBC_SHA256", 0x003d, 5246, 7, 21),
|
||||
// TLS_DH_DSS_WITH_AES_128_CBC_SHA256("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", 0x003e, 5246, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_AES_128_CBC_SHA256("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", 0x003f, 5246, MAX_VALUE, MAX_VALUE),
|
||||
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", 0x0040, 5246, 7, 21),
|
||||
// TLS_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0041, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0042, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0043, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0044, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0045, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", 0x0046, 5932, MAX_VALUE, MAX_VALUE),
|
||||
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", 0x0067, 5246, 7, 21),
|
||||
// TLS_DH_DSS_WITH_AES_256_CBC_SHA256("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", 0x0068, 5246, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_AES_256_CBC_SHA256("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", 0x0069, 5246, MAX_VALUE, MAX_VALUE),
|
||||
TLS_DHE_DSS_WITH_AES_256_CBC_SHA256("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", 0x006a, 5246, 7, 21),
|
||||
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", 0x006b, 5246, 7, 21),
|
||||
TLS_DH_anon_WITH_AES_128_CBC_SHA256("TLS_DH_anon_WITH_AES_128_CBC_SHA256", 0x006c, 5246, 7, 21),
|
||||
TLS_DH_anon_WITH_AES_256_CBC_SHA256("TLS_DH_anon_WITH_AES_256_CBC_SHA256", 0x006d, 5246, 7, 21),
|
||||
// TLS_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0084, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0085, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0086, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0087, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0088, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", 0x0089, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_RC4_128_SHA("TLS_PSK_WITH_RC4_128_SHA", 0x008a, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_3DES_EDE_CBC_SHA("TLS_PSK_WITH_3DES_EDE_CBC_SHA", 0x008b, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_AES_128_CBC_SHA("TLS_PSK_WITH_AES_128_CBC_SHA", 0x008c, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_AES_256_CBC_SHA("TLS_PSK_WITH_AES_256_CBC_SHA", 0x008d, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_RC4_128_SHA("TLS_DHE_PSK_WITH_RC4_128_SHA", 0x008e, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", 0x008f, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_AES_128_CBC_SHA("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", 0x0090, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_AES_256_CBC_SHA("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", 0x0091, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_RC4_128_SHA("TLS_RSA_PSK_WITH_RC4_128_SHA", 0x0092, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", 0x0093, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_AES_128_CBC_SHA("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", 0x0094, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_AES_256_CBC_SHA("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", 0x0095, 4279, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_SEED_CBC_SHA("TLS_RSA_WITH_SEED_CBC_SHA", 0x0096, 4162, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_SEED_CBC_SHA("TLS_DH_DSS_WITH_SEED_CBC_SHA", 0x0097, 4162, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_SEED_CBC_SHA("TLS_DH_RSA_WITH_SEED_CBC_SHA", 0x0098, 4162, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_SEED_CBC_SHA("TLS_DHE_DSS_WITH_SEED_CBC_SHA", 0x0099, 4162, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_SEED_CBC_SHA("TLS_DHE_RSA_WITH_SEED_CBC_SHA", 0x009a, 4162, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_SEED_CBC_SHA("TLS_DH_anon_WITH_SEED_CBC_SHA", 0x009b, 4162, MAX_VALUE, MAX_VALUE),
|
||||
TLS_RSA_WITH_AES_128_GCM_SHA256("TLS_RSA_WITH_AES_128_GCM_SHA256", 0x009c, 5288, 8, 21),
|
||||
TLS_RSA_WITH_AES_256_GCM_SHA384("TLS_RSA_WITH_AES_256_GCM_SHA384", 0x009d, 5288, 8, 21),
|
||||
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", 0x009e, 5288, 8, 21),
|
||||
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", 0x009f, 5288, 8, 21),
|
||||
// TLS_DH_RSA_WITH_AES_128_GCM_SHA256("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", 0x00a0, 5288, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_AES_256_GCM_SHA384("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", 0x00a1, 5288, MAX_VALUE, MAX_VALUE),
|
||||
TLS_DHE_DSS_WITH_AES_128_GCM_SHA256("TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", 0x00a2, 5288, 8, 21),
|
||||
TLS_DHE_DSS_WITH_AES_256_GCM_SHA384("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", 0x00a3, 5288, 8, 21),
|
||||
// TLS_DH_DSS_WITH_AES_128_GCM_SHA256("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", 0x00a4, 5288, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_AES_256_GCM_SHA384("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", 0x00a5, 5288, MAX_VALUE, MAX_VALUE),
|
||||
TLS_DH_anon_WITH_AES_128_GCM_SHA256("TLS_DH_anon_WITH_AES_128_GCM_SHA256", 0x00a6, 5288, 8, 21),
|
||||
TLS_DH_anon_WITH_AES_256_GCM_SHA384("TLS_DH_anon_WITH_AES_256_GCM_SHA384", 0x00a7, 5288, 8, 21),
|
||||
// TLS_PSK_WITH_AES_128_GCM_SHA256("TLS_PSK_WITH_AES_128_GCM_SHA256", 0x00a8, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_AES_256_GCM_SHA384("TLS_PSK_WITH_AES_256_GCM_SHA384", 0x00a9, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_AES_128_GCM_SHA256("TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", 0x00aa, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_AES_256_GCM_SHA384("TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", 0x00ab, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_AES_128_GCM_SHA256("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", 0x00ac, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_AES_256_GCM_SHA384("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", 0x00ad, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_AES_128_CBC_SHA256("TLS_PSK_WITH_AES_128_CBC_SHA256", 0x00ae, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_AES_256_CBC_SHA384("TLS_PSK_WITH_AES_256_CBC_SHA384", 0x00af, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_NULL_SHA256("TLS_PSK_WITH_NULL_SHA256", 0x00b0, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_NULL_SHA384("TLS_PSK_WITH_NULL_SHA384", 0x00b1, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_AES_128_CBC_SHA256("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", 0x00b2, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_AES_256_CBC_SHA384("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", 0x00b3, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_NULL_SHA256("TLS_DHE_PSK_WITH_NULL_SHA256", 0x00b4, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_NULL_SHA384("TLS_DHE_PSK_WITH_NULL_SHA384", 0x00b5, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_AES_128_CBC_SHA256("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", 0x00b6, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_AES_256_CBC_SHA384("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", 0x00b7, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_NULL_SHA256("TLS_RSA_PSK_WITH_NULL_SHA256", 0x00b8, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_NULL_SHA384("TLS_RSA_PSK_WITH_NULL_SHA384", 0x00b9, 5487, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00ba, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bb, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00bc, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bd, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00be, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", 0x00bf, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c0, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c1, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c2, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c3, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c4, 5932, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", 0x00c5, 5932, MAX_VALUE, MAX_VALUE),
|
||||
TLS_EMPTY_RENEGOTIATION_INFO_SCSV("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", 0x00ff, 5746, 6, 14),
|
||||
TLS_ECDH_ECDSA_WITH_NULL_SHA("TLS_ECDH_ECDSA_WITH_NULL_SHA", 0xc001, 4492, 7, 14),
|
||||
TLS_ECDH_ECDSA_WITH_RC4_128_SHA("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", 0xc002, 4492, 7, 14),
|
||||
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc003, 4492, 7, 14),
|
||||
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", 0xc004, 4492, 7, 14),
|
||||
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", 0xc005, 4492, 7, 14),
|
||||
TLS_ECDHE_ECDSA_WITH_NULL_SHA("TLS_ECDHE_ECDSA_WITH_NULL_SHA", 0xc006, 4492, 7, 14),
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", 0xc007, 4492, 7, 14),
|
||||
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc008, 4492, 7, 14),
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 0xc009, 4492, 7, 14),
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 0xc00a, 4492, 7, 14),
|
||||
TLS_ECDH_RSA_WITH_NULL_SHA("TLS_ECDH_RSA_WITH_NULL_SHA", 0xc00b, 4492, 7, 14),
|
||||
TLS_ECDH_RSA_WITH_RC4_128_SHA("TLS_ECDH_RSA_WITH_RC4_128_SHA", 0xc00c, 4492, 7, 14),
|
||||
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", 0xc00d, 4492, 7, 14),
|
||||
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", 0xc00e, 4492, 7, 14),
|
||||
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", 0xc00f, 4492, 7, 14),
|
||||
TLS_ECDHE_RSA_WITH_NULL_SHA("TLS_ECDHE_RSA_WITH_NULL_SHA", 0xc010, 4492, 7, 14),
|
||||
TLS_ECDHE_RSA_WITH_RC4_128_SHA("TLS_ECDHE_RSA_WITH_RC4_128_SHA", 0xc011, 4492, 7, 14),
|
||||
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", 0xc012, 4492, 7, 14),
|
||||
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 0xc013, 4492, 7, 14),
|
||||
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 0xc014, 4492, 7, 14),
|
||||
TLS_ECDH_anon_WITH_NULL_SHA("TLS_ECDH_anon_WITH_NULL_SHA", 0xc015, 4492, 7, 14),
|
||||
TLS_ECDH_anon_WITH_RC4_128_SHA("TLS_ECDH_anon_WITH_RC4_128_SHA", 0xc016, 4492, 7, 14),
|
||||
TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", 0xc017, 4492, 7, 14),
|
||||
TLS_ECDH_anon_WITH_AES_128_CBC_SHA("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", 0xc018, 4492, 7, 14),
|
||||
TLS_ECDH_anon_WITH_AES_256_CBC_SHA("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", 0xc019, 4492, 7, 14),
|
||||
// TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", 0xc01a, 5054, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", 0xc01b, 5054, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", 0xc01c, 5054, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_SRP_SHA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", 0xc01d, 5054, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", 0xc01e, 5054, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", 0xc01f, 5054, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_SRP_SHA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", 0xc020, 5054, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", 0xc021, 5054, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", 0xc022, 5054, MAX_VALUE, MAX_VALUE),
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 0xc023, 5289, 7, 21),
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 0xc024, 5289, 7, 21),
|
||||
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", 0xc025, 5289, 7, 21),
|
||||
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", 0xc026, 5289, 7, 21),
|
||||
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 0xc027, 5289, 7, 21),
|
||||
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", 0xc028, 5289, 7, 21),
|
||||
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", 0xc029, 5289, 7, 21),
|
||||
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", 0xc02a, 5289, 7, 21),
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02b, 5289, 8, 21),
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02c, 5289, 8, 21),
|
||||
TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02d, 5289, 8, 21),
|
||||
TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02e, 5289, 8, 21),
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 0xc02f, 5289, 8, 21),
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 0xc030, 5289, 8, 21),
|
||||
TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", 0xc031, 5289, 8, 21),
|
||||
TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", 0xc032, 5289, 8, 21),
|
||||
// TLS_ECDHE_PSK_WITH_RC4_128_SHA("TLS_ECDHE_PSK_WITH_RC4_128_SHA", 0xc033, 5489, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", 0xc034, 5489, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", 0xc035, 5489, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", 0xc036, 5489, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", 0xc037, 5489, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", 0xc038, 5489, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_NULL_SHA("TLS_ECDHE_PSK_WITH_NULL_SHA", 0xc039, 5489, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_NULL_SHA256("TLS_ECDHE_PSK_WITH_NULL_SHA256", 0xc03a, 5489, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_NULL_SHA384("TLS_ECDHE_PSK_WITH_NULL_SHA384", 0xc03b, 5489, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_ARIA_128_CBC_SHA256("TLS_RSA_WITH_ARIA_128_CBC_SHA256", 0xc03c, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_ARIA_256_CBC_SHA384("TLS_RSA_WITH_ARIA_256_CBC_SHA384", 0xc03d, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", 0xc03e, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", 0xc03f, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc040, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc041, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", 0xc042, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", 0xc043, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc044, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc045, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_ARIA_128_CBC_SHA256("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", 0xc046, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_ARIA_256_CBC_SHA384("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", 0xc047, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc048, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc049, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc04a, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc04b, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04c, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04d, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04e, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04f, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_ARIA_128_GCM_SHA256("TLS_RSA_WITH_ARIA_128_GCM_SHA256", 0xc050, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_ARIA_256_GCM_SHA384("TLS_RSA_WITH_ARIA_256_GCM_SHA384", 0xc051, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc052, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc053, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc054, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc055, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", 0xc056, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", 0xc057, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", 0xc058, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", 0xc059, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_ARIA_128_GCM_SHA256("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", 0xc05a, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_ARIA_256_GCM_SHA384("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", 0xc05b, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05c, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05d, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05e, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05f, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc060, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc061, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc062, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc063, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_ARIA_128_CBC_SHA256("TLS_PSK_WITH_ARIA_128_CBC_SHA256", 0xc064, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_ARIA_256_CBC_SHA384("TLS_PSK_WITH_ARIA_256_CBC_SHA384", 0xc065, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc066, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc067, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", 0xc068, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", 0xc069, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_ARIA_128_GCM_SHA256("TLS_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06a, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_ARIA_256_GCM_SHA384("TLS_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06b, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06c, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06d, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06e, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06f, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc070, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc071, 6209, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc072, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc073, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc074, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc075, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc076, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc077, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc078, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc079, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07a, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07b, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07c, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07d, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07e, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07f, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc080, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc081, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc082, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc083, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", 0xc084, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", 0xc085, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc086, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc087, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc088, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc089, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08a, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08b, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08c, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08d, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc08e, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc08f, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc090, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc091, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc092, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc093, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc094, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc095, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc096, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc097, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc098, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc099, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc09a, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc09b, 6367, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_AES_128_CCM("TLS_RSA_WITH_AES_128_CCM", 0xc09c, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_AES_256_CCM("TLS_RSA_WITH_AES_256_CCM", 0xc09d, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_AES_128_CCM("TLS_DHE_RSA_WITH_AES_128_CCM", 0xc09e, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_AES_256_CCM("TLS_DHE_RSA_WITH_AES_256_CCM", 0xc09f, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_AES_128_CCM_8("TLS_RSA_WITH_AES_128_CCM_8", 0xc0a0, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_RSA_WITH_AES_256_CCM_8("TLS_RSA_WITH_AES_256_CCM_8", 0xc0a1, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_AES_128_CCM_8("TLS_DHE_RSA_WITH_AES_128_CCM_8", 0xc0a2, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_RSA_WITH_AES_256_CCM_8("TLS_DHE_RSA_WITH_AES_256_CCM_8", 0xc0a3, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_AES_128_CCM("TLS_PSK_WITH_AES_128_CCM", 0xc0a4, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_AES_256_CCM("TLS_PSK_WITH_AES_256_CCM", 0xc0a5, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_AES_128_CCM("TLS_DHE_PSK_WITH_AES_128_CCM", 0xc0a6, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_DHE_PSK_WITH_AES_256_CCM("TLS_DHE_PSK_WITH_AES_256_CCM", 0xc0a7, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_AES_128_CCM_8("TLS_PSK_WITH_AES_128_CCM_8", 0xc0a8, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_WITH_AES_256_CCM_8("TLS_PSK_WITH_AES_256_CCM_8", 0xc0a9, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_DHE_WITH_AES_128_CCM_8("TLS_PSK_DHE_WITH_AES_128_CCM_8", 0xc0aa, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_PSK_DHE_WITH_AES_256_CCM_8("TLS_PSK_DHE_WITH_AES_256_CCM_8", 0xc0ab, 6655, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_AES_128_CCM("TLS_ECDHE_ECDSA_WITH_AES_128_CCM", 0xc0ac, 7251, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_AES_256_CCM("TLS_ECDHE_ECDSA_WITH_AES_256_CCM", 0xc0ad, 7251, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", 0xc0ae, 7251, MAX_VALUE, MAX_VALUE),
|
||||
// TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", 0xc0af, 7251, MAX_VALUE, MAX_VALUE),
|
||||
;
|
||||
|
||||
final String javaName;
|
||||
|
||||
/**
|
||||
* @param javaName the name used by Java APIs for this cipher suite. Different than the IANA name
|
||||
* for older cipher suites because the prefix is {@code SSL_} instead of {@code TLS_}.
|
||||
* @param value the integer identifier for this cipher suite. (Documentation only.)
|
||||
* @param rfc the RFC describing this cipher suite. (Documentation only.)
|
||||
* @param sinceJavaVersion the first major Java release supporting this cipher suite.
|
||||
* @param sinceAndroidVersion the first Android SDK version supporting this cipher suite.
|
||||
*/
|
||||
private CipherSuite(
|
||||
String javaName, int value, int rfc, int sinceJavaVersion, int sinceAndroidVersion) {
|
||||
this.javaName = javaName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Java name of this cipher suite. For some older cipher suites the Java name has the
|
||||
* prefix {@code SSL_}, causing the Java name to be different from the {@linkplain #name() enum
|
||||
* name} which is always prefixed {@code TLS_}.
|
||||
*/
|
||||
public String javaName() {
|
||||
return javaName;
|
||||
}
|
||||
|
||||
public static CipherSuite forJavaName(String javaName) {
|
||||
return javaName.startsWith("SSL_")
|
||||
? valueOf("TLS_" + javaName.substring(4))
|
||||
: valueOf(javaName);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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 okhttp3;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* The sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection. May be used for multiple
|
||||
* HTTP request/response exchanges. Connections may be direct to the origin server or via a proxy.
|
||||
*
|
||||
* <p>Typically instances of this class are created, connected and exercised automatically by the
|
||||
* HTTP client. Applications may use this class to monitor HTTP connections as members of a
|
||||
* {@linkplain ConnectionPool connection pool}.
|
||||
*
|
||||
* <p>Do not confuse this class with the misnamed {@code HttpURLConnection}, which isn't so much a
|
||||
* connection as a single request/response exchange.
|
||||
*
|
||||
* <h3>Modern TLS</h3>
|
||||
*
|
||||
* <p>There are tradeoffs when selecting which options to include when negotiating a secure
|
||||
* connection to a remote host. Newer TLS options are quite useful:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Server Name Indication (SNI) enables one IP address to negotiate secure connections for
|
||||
* multiple domain names.
|
||||
* <li>Application Layer Protocol Negotiation (ALPN) enables the HTTPS port (443) to be used for
|
||||
* different HTTP and SPDY protocols.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Unfortunately, older HTTPS servers refuse to connect when such options are presented. Rather
|
||||
* than avoiding these options entirely, this class allows a connection to be attempted with modern
|
||||
* options and then retried without them should the attempt fail.
|
||||
*
|
||||
* <h3>Connection Reuse</h3>
|
||||
*
|
||||
* <p>Each connection can carry a varying number streams, depending on the underlying protocol being
|
||||
* used. HTTP/1.x connections can carry either zero or one streams. HTTP/2 connections can carry any
|
||||
* number of streams, dynamically configured with {@code SETTINGS_MAX_CONCURRENT_STREAMS}. A
|
||||
* connection currently carrying zero streams is an idle stream. We keep it alive because reusing an
|
||||
* existing connection is typically faster than establishing a new one.
|
||||
*
|
||||
* <p>When a single logical call requires multiple streams due to redirects or authorization
|
||||
* challenges, we prefer to use the same physical connection for all streams in the sequence. There
|
||||
* are potential performance and behavior consequences to this preference. To support this feature,
|
||||
* this class separates <i>allocations</i> from <i>streams</i>. An allocation is created by a call,
|
||||
* used for one or more streams, and then released. An allocated connection won't be stolen by other
|
||||
* calls while a redirect or authorization challenge is being handled.
|
||||
*
|
||||
* <p>When the maximum concurrent streams limit is reduced, some allocations will be rescinded.
|
||||
* Attempting to create new streams on these allocations will fail.
|
||||
*
|
||||
* <p>Note that an allocation may be released before its stream is completed. This is intended to
|
||||
* make bookkeeping easier for the caller: releasing the allocation as soon as the terminal stream
|
||||
* has been found. But only complete the stream once its data stream has been exhausted.
|
||||
*/
|
||||
public interface Connection {
|
||||
/** Returns the route used by this connection. */
|
||||
Route route();
|
||||
|
||||
/**
|
||||
* Returns the socket that this connection is using. Returns an {@linkplain
|
||||
* javax.net.ssl.SSLSocket SSL socket} if this connection is HTTPS. If this is an HTTP/2 or SPDY
|
||||
* connection the socket may be shared by multiple concurrent calls.
|
||||
*/
|
||||
Socket socket();
|
||||
|
||||
/**
|
||||
* Returns the TLS handshake used to establish this connection, or null if the connection is not
|
||||
* HTTPS.
|
||||
*/
|
||||
Handshake handshake();
|
||||
|
||||
/**
|
||||
* Returns the protocol negotiated by this connection, or {@link Protocol#HTTP_1_1} if no protocol
|
||||
* has been negotiated. This method returns {@link Protocol#HTTP_1_1} even if the remote peer is
|
||||
* using {@link Protocol#HTTP_1_0}.
|
||||
*/
|
||||
Protocol protocol();
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/*
|
||||
* 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 okhttp3;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import okhttp3.internal.Internal;
|
||||
import okhttp3.internal.RouteDatabase;
|
||||
import okhttp3.internal.Util;
|
||||
import okhttp3.internal.http.StreamAllocation;
|
||||
import okhttp3.internal.io.RealConnection;
|
||||
|
||||
import static okhttp3.internal.Util.closeQuietly;
|
||||
|
||||
/**
|
||||
* Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP requests that share
|
||||
* the same {@link Address} may share a {@link Connection}. This class implements the policy of
|
||||
* which connections to keep open for future use.
|
||||
*/
|
||||
public final class ConnectionPool {
|
||||
/**
|
||||
* Background threads are used to cleanup expired connections. There will be at most a single
|
||||
* thread running per connection pool. The thread pool executor permits the pool itself to be
|
||||
* garbage collected.
|
||||
*/
|
||||
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
|
||||
Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
|
||||
|
||||
/** The maximum number of idle connections for each address. */
|
||||
private final int maxIdleConnections;
|
||||
private final long keepAliveDurationNs;
|
||||
private final Runnable cleanupRunnable = new Runnable() {
|
||||
@Override public void run() {
|
||||
while (true) {
|
||||
long waitNanos = cleanup(System.nanoTime());
|
||||
if (waitNanos == -1) return;
|
||||
if (waitNanos > 0) {
|
||||
long waitMillis = waitNanos / 1000000L;
|
||||
waitNanos -= (waitMillis * 1000000L);
|
||||
synchronized (ConnectionPool.this) {
|
||||
try {
|
||||
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final Deque<RealConnection> connections = new ArrayDeque<>();
|
||||
final RouteDatabase routeDatabase = new RouteDatabase();
|
||||
boolean cleanupRunning;
|
||||
|
||||
/**
|
||||
* Create a new connection pool with tuning parameters appropriate for a single-user application.
|
||||
* The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
|
||||
* this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
|
||||
*/
|
||||
public ConnectionPool() {
|
||||
this(5, 5, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
|
||||
this.maxIdleConnections = maxIdleConnections;
|
||||
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
|
||||
|
||||
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
|
||||
if (keepAliveDuration <= 0) {
|
||||
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the number of idle connections in the pool. */
|
||||
public synchronized int idleConnectionCount() {
|
||||
int total = 0;
|
||||
for (RealConnection connection : connections) {
|
||||
if (connection.allocations.isEmpty()) total++;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns total number of connections in the pool. Note that prior to OkHttp 2.7 this included
|
||||
* only idle connections and SPDY connections. Since OkHttp 2.7 this includes all connections,
|
||||
* both active and inactive. Use {@link #idleConnectionCount()} to count connections not currently
|
||||
* in use.
|
||||
*/
|
||||
public synchronized int connectionCount() {
|
||||
return connections.size();
|
||||
}
|
||||
|
||||
/** Returns a recycled connection to {@code address}, or null if no such connection exists. */
|
||||
RealConnection get(Address address, StreamAllocation streamAllocation) {
|
||||
assert (Thread.holdsLock(this));
|
||||
for (RealConnection connection : connections) {
|
||||
if (connection.allocations.size() < connection.allocationLimit
|
||||
&& address.equals(connection.route().address)
|
||||
&& !connection.noNewStreams) {
|
||||
streamAllocation.acquire(connection);
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void put(RealConnection connection) {
|
||||
assert (Thread.holdsLock(this));
|
||||
if (!cleanupRunning) {
|
||||
cleanupRunning = true;
|
||||
executor.execute(cleanupRunnable);
|
||||
}
|
||||
connections.add(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify this pool that {@code connection} has become idle. Returns true if the connection has
|
||||
* been removed from the pool and should be closed.
|
||||
*/
|
||||
boolean connectionBecameIdle(RealConnection connection) {
|
||||
assert (Thread.holdsLock(this));
|
||||
if (connection.noNewStreams || maxIdleConnections == 0) {
|
||||
connections.remove(connection);
|
||||
return true;
|
||||
} else {
|
||||
notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Close and remove all idle connections in the pool. */
|
||||
public void evictAll() {
|
||||
List<RealConnection> evictedConnections = new ArrayList<>();
|
||||
synchronized (this) {
|
||||
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
|
||||
RealConnection connection = i.next();
|
||||
if (connection.allocations.isEmpty()) {
|
||||
connection.noNewStreams = true;
|
||||
evictedConnections.add(connection);
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (RealConnection connection : evictedConnections) {
|
||||
closeQuietly(connection.socket());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs maintenance on this pool, evicting the connection that has been idle the longest if
|
||||
* either it has exceeded the keep alive limit or the idle connections limit.
|
||||
*
|
||||
* <p>Returns the duration in nanos to sleep until the next scheduled call to this method. Returns
|
||||
* -1 if no further cleanups are required.
|
||||
*/
|
||||
long cleanup(long now) {
|
||||
int inUseConnectionCount = 0;
|
||||
int idleConnectionCount = 0;
|
||||
RealConnection longestIdleConnection = null;
|
||||
long longestIdleDurationNs = Long.MIN_VALUE;
|
||||
|
||||
// Find either a connection to evict, or the time that the next eviction is due.
|
||||
synchronized (this) {
|
||||
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
|
||||
RealConnection connection = i.next();
|
||||
|
||||
// If the connection is in use, keep searching.
|
||||
if (pruneAndGetAllocationCount(connection, now) > 0) {
|
||||
inUseConnectionCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
idleConnectionCount++;
|
||||
|
||||
// If the connection is ready to be evicted, we're done.
|
||||
long idleDurationNs = now - connection.idleAtNanos;
|
||||
if (idleDurationNs > longestIdleDurationNs) {
|
||||
longestIdleDurationNs = idleDurationNs;
|
||||
longestIdleConnection = connection;
|
||||
}
|
||||
}
|
||||
|
||||
if (longestIdleDurationNs >= this.keepAliveDurationNs
|
||||
|| idleConnectionCount > this.maxIdleConnections) {
|
||||
// We've found a connection to evict. Remove it from the list, then close it below (outside
|
||||
// of the synchronized block).
|
||||
connections.remove(longestIdleConnection);
|
||||
} else if (idleConnectionCount > 0) {
|
||||
// A connection will be ready to evict soon.
|
||||
return keepAliveDurationNs - longestIdleDurationNs;
|
||||
} else if (inUseConnectionCount > 0) {
|
||||
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
|
||||
return keepAliveDurationNs;
|
||||
} else {
|
||||
// No connections, idle or in use.
|
||||
cleanupRunning = false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
closeQuietly(longestIdleConnection.socket());
|
||||
|
||||
// Cleanup again immediately.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prunes any leaked allocations and then returns the number of remaining live allocations on
|
||||
* {@code connection}. Allocations are leaked if the connection is tracking them but the
|
||||
* application code has abandoned them. Leak detection is imprecise and relies on garbage
|
||||
* collection.
|
||||
*/
|
||||
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
|
||||
List<Reference<StreamAllocation>> references = connection.allocations;
|
||||
for (int i = 0; i < references.size(); ) {
|
||||
Reference<StreamAllocation> reference = references.get(i);
|
||||
|
||||
if (reference.get() != null) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We've discovered a leaked allocation. This is an application bug.
|
||||
Internal.logger.warning("A connection to " + connection.route().address().url()
|
||||
+ " was leaked. Did you forget to close a response body?");
|
||||
references.remove(i);
|
||||
connection.noNewStreams = true;
|
||||
|
||||
// If this was the last allocation, the connection is eligible for immediate eviction.
|
||||
if (references.isEmpty()) {
|
||||
connection.idleAtNanos = now - keepAliveDurationNs;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return references.size();
|
||||
}
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import static okhttp3.internal.Util.concat;
|
||||
import static okhttp3.internal.Util.contains;
|
||||
import static okhttp3.internal.Util.immutableList;
|
||||
import static okhttp3.internal.Util.intersect;
|
||||
|
||||
/**
|
||||
* Specifies configuration for the socket connection that HTTP traffic travels through. For {@code
|
||||
* https:} URLs, this includes the TLS version and cipher suites to use when negotiating a secure
|
||||
* connection.
|
||||
*
|
||||
* <p>The TLS versions configured in a connection spec are only be used if they are also enabled in
|
||||
* the SSL socket. For example, if an SSL socket does not have TLS 1.2 enabled, it will not be used
|
||||
* even if it is present on the connection spec. The same policy also applies to cipher suites.
|
||||
*
|
||||
* <p>Use {@link Builder#allEnabledTlsVersions()} and {@link Builder#allEnabledCipherSuites} to
|
||||
* defer all feature selection to the underlying SSL socket.
|
||||
*/
|
||||
public final class ConnectionSpec {
|
||||
|
||||
// This is a subset of the cipher suites supported in Chrome 46, current as of 2015-11-05.
|
||||
// All of these suites are available on Android 5.0; earlier releases support a subset of
|
||||
// these suites. https://github.com/square/okhttp/issues/330
|
||||
private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
|
||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
|
||||
// Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
|
||||
// continue to include them until better suites are commonly available. For example, none
|
||||
// of the better cipher suites listed above shipped with Android 4.4 or Java 7.
|
||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
};
|
||||
|
||||
/** A modern TLS connection with extensions like SNI and ALPN available. */
|
||||
public static final ConnectionSpec MODERN_TLS = new Builder(true)
|
||||
.cipherSuites(APPROVED_CIPHER_SUITES)
|
||||
.tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
|
||||
.supportsTlsExtensions(true)
|
||||
.build();
|
||||
|
||||
/** A backwards-compatible fallback connection for interop with obsolete servers. */
|
||||
public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS)
|
||||
.tlsVersions(TlsVersion.TLS_1_0)
|
||||
.supportsTlsExtensions(true)
|
||||
.build();
|
||||
|
||||
/** Unencrypted, unauthenticated connections for {@code http:} URLs. */
|
||||
public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
|
||||
|
||||
private final boolean tls;
|
||||
private final boolean supportsTlsExtensions;
|
||||
private final String[] cipherSuites;
|
||||
private final String[] tlsVersions;
|
||||
|
||||
private ConnectionSpec(Builder builder) {
|
||||
this.tls = builder.tls;
|
||||
this.cipherSuites = builder.cipherSuites;
|
||||
this.tlsVersions = builder.tlsVersions;
|
||||
this.supportsTlsExtensions = builder.supportsTlsExtensions;
|
||||
}
|
||||
|
||||
public boolean isTls() {
|
||||
return tls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cipher suites to use for a connection. Returns {@code null} if all of the SSL
|
||||
* socket's enabled cipher suites should be used.
|
||||
*/
|
||||
public List<CipherSuite> cipherSuites() {
|
||||
if (cipherSuites == null) return null;
|
||||
|
||||
CipherSuite[] result = new CipherSuite[cipherSuites.length];
|
||||
for (int i = 0; i < cipherSuites.length; i++) {
|
||||
result[i] = CipherSuite.forJavaName(cipherSuites[i]);
|
||||
}
|
||||
return immutableList(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TLS versions to use when negotiating a connection. Returns {@code null} if all of
|
||||
* the SSL socket's enabled TLS versions should be used.
|
||||
*/
|
||||
public List<TlsVersion> tlsVersions() {
|
||||
if (tlsVersions == null) return null;
|
||||
|
||||
TlsVersion[] result = new TlsVersion[tlsVersions.length];
|
||||
for (int i = 0; i < tlsVersions.length; i++) {
|
||||
result[i] = TlsVersion.forJavaName(tlsVersions[i]);
|
||||
}
|
||||
return immutableList(result);
|
||||
}
|
||||
|
||||
public boolean supportsTlsExtensions() {
|
||||
return supportsTlsExtensions;
|
||||
}
|
||||
|
||||
/** Applies this spec to {@code sslSocket}. */
|
||||
void apply(SSLSocket sslSocket, boolean isFallback) {
|
||||
ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
|
||||
|
||||
if (specToApply.tlsVersions != null) {
|
||||
sslSocket.setEnabledProtocols(specToApply.tlsVersions);
|
||||
}
|
||||
if (specToApply.cipherSuites != null) {
|
||||
sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this that omits cipher suites and TLS versions not enabled by {@code
|
||||
* sslSocket}.
|
||||
*/
|
||||
private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) {
|
||||
String[] cipherSuitesIntersection = cipherSuites != null
|
||||
? intersect(String.class, cipherSuites, sslSocket.getEnabledCipherSuites())
|
||||
: sslSocket.getEnabledCipherSuites();
|
||||
String[] tlsVersionsIntersection = tlsVersions != null
|
||||
? intersect(String.class, tlsVersions, sslSocket.getEnabledProtocols())
|
||||
: sslSocket.getEnabledProtocols();
|
||||
|
||||
// In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
|
||||
// the SCSV cipher is added to signal that a protocol fallback has taken place.
|
||||
if (isFallback && contains(sslSocket.getSupportedCipherSuites(), "TLS_FALLBACK_SCSV")) {
|
||||
cipherSuitesIntersection = concat(cipherSuitesIntersection, "TLS_FALLBACK_SCSV");
|
||||
}
|
||||
|
||||
return new Builder(this)
|
||||
.cipherSuites(cipherSuitesIntersection)
|
||||
.tlsVersions(tlsVersionsIntersection)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the socket, as currently configured, supports this connection spec. In
|
||||
* order for a socket to be compatible the enabled cipher suites and protocols must intersect.
|
||||
*
|
||||
* <p>For cipher suites, at least one of the {@link #cipherSuites() required cipher suites} must
|
||||
* match the socket's enabled cipher suites. If there are no required cipher suites the socket
|
||||
* must have at least one cipher suite enabled.
|
||||
*
|
||||
* <p>For protocols, at least one of the {@link #tlsVersions() required protocols} must match the
|
||||
* socket's enabled protocols.
|
||||
*/
|
||||
public boolean isCompatible(SSLSocket socket) {
|
||||
if (!tls) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tlsVersions != null
|
||||
&& !nonEmptyIntersection(tlsVersions, socket.getEnabledProtocols())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cipherSuites != null
|
||||
&& !nonEmptyIntersection(cipherSuites, socket.getEnabledCipherSuites())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* An N*M intersection that terminates if any intersection is found. The sizes of both arguments
|
||||
* are assumed to be so small, and the likelihood of an intersection so great, that it is not
|
||||
* worth the CPU cost of sorting or the memory cost of hashing.
|
||||
*/
|
||||
private static boolean nonEmptyIntersection(String[] a, String[] b) {
|
||||
if (a == null || b == null || a.length == 0 || b.length == 0) {
|
||||
return false;
|
||||
}
|
||||
for (String toFind : a) {
|
||||
if (contains(b, toFind)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
if (!(other instanceof ConnectionSpec)) return false;
|
||||
if (other == this) return true;
|
||||
|
||||
ConnectionSpec that = (ConnectionSpec) other;
|
||||
if (this.tls != that.tls) return false;
|
||||
|
||||
if (tls) {
|
||||
if (!Arrays.equals(this.cipherSuites, that.cipherSuites)) return false;
|
||||
if (!Arrays.equals(this.tlsVersions, that.tlsVersions)) return false;
|
||||
if (this.supportsTlsExtensions != that.supportsTlsExtensions) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 17;
|
||||
if (tls) {
|
||||
result = 31 * result + Arrays.hashCode(cipherSuites);
|
||||
result = 31 * result + Arrays.hashCode(tlsVersions);
|
||||
result = 31 * result + (supportsTlsExtensions ? 0 : 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
if (!tls) {
|
||||
return "ConnectionSpec()";
|
||||
}
|
||||
|
||||
String cipherSuitesString = cipherSuites != null ? cipherSuites().toString() : "[all enabled]";
|
||||
String tlsVersionsString = tlsVersions != null ? tlsVersions().toString() : "[all enabled]";
|
||||
return "ConnectionSpec("
|
||||
+ "cipherSuites=" + cipherSuitesString
|
||||
+ ", tlsVersions=" + tlsVersionsString
|
||||
+ ", supportsTlsExtensions=" + supportsTlsExtensions
|
||||
+ ")";
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private boolean tls;
|
||||
private String[] cipherSuites;
|
||||
private String[] tlsVersions;
|
||||
private boolean supportsTlsExtensions;
|
||||
|
||||
Builder(boolean tls) {
|
||||
this.tls = tls;
|
||||
}
|
||||
|
||||
public Builder(ConnectionSpec connectionSpec) {
|
||||
this.tls = connectionSpec.tls;
|
||||
this.cipherSuites = connectionSpec.cipherSuites;
|
||||
this.tlsVersions = connectionSpec.tlsVersions;
|
||||
this.supportsTlsExtensions = connectionSpec.supportsTlsExtensions;
|
||||
}
|
||||
|
||||
public Builder allEnabledCipherSuites() {
|
||||
if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
|
||||
this.cipherSuites = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cipherSuites(CipherSuite... cipherSuites) {
|
||||
if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
|
||||
|
||||
String[] strings = new String[cipherSuites.length];
|
||||
for (int i = 0; i < cipherSuites.length; i++) {
|
||||
strings[i] = cipherSuites[i].javaName;
|
||||
}
|
||||
return cipherSuites(strings);
|
||||
}
|
||||
|
||||
public Builder cipherSuites(String... cipherSuites) {
|
||||
if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
|
||||
|
||||
if (cipherSuites.length == 0) {
|
||||
throw new IllegalArgumentException("At least one cipher suite is required");
|
||||
}
|
||||
|
||||
this.cipherSuites = cipherSuites.clone(); // Defensive copy.
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allEnabledTlsVersions() {
|
||||
if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
|
||||
this.tlsVersions = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tlsVersions(TlsVersion... tlsVersions) {
|
||||
if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
|
||||
|
||||
String[] strings = new String[tlsVersions.length];
|
||||
for (int i = 0; i < tlsVersions.length; i++) {
|
||||
strings[i] = tlsVersions[i].javaName;
|
||||
}
|
||||
|
||||
return tlsVersions(strings);
|
||||
}
|
||||
|
||||
public Builder tlsVersions(String... tlsVersions) {
|
||||
if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
|
||||
|
||||
if (tlsVersions.length == 0) {
|
||||
throw new IllegalArgumentException("At least one TLS version is required");
|
||||
}
|
||||
|
||||
this.tlsVersions = tlsVersions.clone(); // Defensive copy.
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder supportsTlsExtensions(boolean supportsTlsExtensions) {
|
||||
if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections");
|
||||
this.supportsTlsExtensions = supportsTlsExtensions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConnectionSpec build() {
|
||||
return new ConnectionSpec(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,590 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import okhttp3.internal.Util;
|
||||
import okhttp3.internal.http.HttpDate;
|
||||
|
||||
import static okhttp3.internal.Util.UTC;
|
||||
import static okhttp3.internal.Util.delimiterOffset;
|
||||
import static okhttp3.internal.Util.domainToAscii;
|
||||
import static okhttp3.internal.Util.trimSubstring;
|
||||
import static okhttp3.internal.Util.verifyAsIpAddress;
|
||||
|
||||
/**
|
||||
* An <a href="http://tools.ietf.org/html/rfc6265">RFC 6265</a> Cookie.
|
||||
*
|
||||
* <p>This class doesn't support additional attributes on cookies, like <a
|
||||
* href="https://code.google.com/p/chromium/issues/detail?id=232693">Chromium's Priority=HIGH
|
||||
* extension</a>.
|
||||
*/
|
||||
public final class Cookie {
|
||||
private static final Pattern YEAR_PATTERN
|
||||
= Pattern.compile("(\\d{2,4})[^\\d]*");
|
||||
private static final Pattern MONTH_PATTERN
|
||||
= Pattern.compile("(?i)(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec).*");
|
||||
private static final Pattern DAY_OF_MONTH_PATTERN
|
||||
= Pattern.compile("(\\d{1,2})[^\\d]*");
|
||||
private static final Pattern TIME_PATTERN
|
||||
= Pattern.compile("(\\d{1,2}):(\\d{1,2}):(\\d{1,2})[^\\d]*");
|
||||
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final long expiresAt;
|
||||
private final String domain;
|
||||
private final String path;
|
||||
private final boolean secure;
|
||||
private final boolean httpOnly;
|
||||
|
||||
private final boolean persistent; // True if 'expires' or 'max-age' is present.
|
||||
private final boolean hostOnly; // True unless 'domain' is present.
|
||||
|
||||
private Cookie(String name, String value, long expiresAt, String domain, String path,
|
||||
boolean secure, boolean httpOnly, boolean hostOnly, boolean persistent) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.expiresAt = expiresAt;
|
||||
this.domain = domain;
|
||||
this.path = path;
|
||||
this.secure = secure;
|
||||
this.httpOnly = httpOnly;
|
||||
this.hostOnly = hostOnly;
|
||||
this.persistent = persistent;
|
||||
}
|
||||
|
||||
private Cookie(Builder builder) {
|
||||
if (builder.name == null) throw new IllegalArgumentException("builder.name == null");
|
||||
if (builder.value == null) throw new IllegalArgumentException("builder.value == null");
|
||||
if (builder.domain == null) throw new IllegalArgumentException("builder.domain == null");
|
||||
|
||||
this.name = builder.name;
|
||||
this.value = builder.value;
|
||||
this.expiresAt = builder.expiresAt;
|
||||
this.domain = builder.domain;
|
||||
this.path = builder.path;
|
||||
this.secure = builder.secure;
|
||||
this.httpOnly = builder.httpOnly;
|
||||
this.persistent = builder.persistent;
|
||||
this.hostOnly = builder.hostOnly;
|
||||
}
|
||||
|
||||
/** Returns a non-empty string with this cookie's name. */
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** Returns a possibly-empty string with this cookie's value. */
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns true if this cookie expires at the end of the current session. */
|
||||
public boolean persistent() {
|
||||
return persistent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time that this cookie expires, in the same format as {@link
|
||||
* System#currentTimeMillis()}. This is December 31, 9999 if the cookie is {@linkplain
|
||||
* #persistent() not persistent}, in which case it will expire at the end of the current session.
|
||||
*
|
||||
* <p>This may return a value less than the current time, in which case the cookie is already
|
||||
* expired. Webservers may return expired cookies as a mechanism to delete previously set cookies
|
||||
* that may or may not themselves be expired.
|
||||
*/
|
||||
public long expiresAt() {
|
||||
return expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this cookie's domain should be interpreted as a single host name, or false if
|
||||
* it should be interpreted as a pattern. This flag will be false if its {@code Set-Cookie} header
|
||||
* included a {@code domain} attribute.
|
||||
*
|
||||
* <p>For example, suppose the cookie's domain is {@code example.com}. If this flag is true it
|
||||
* matches <strong>only</strong> {@code example.com}. If this flag is false it matches {@code
|
||||
* example.com} and all subdomains including {@code api.example.com}, {@code www.example.com}, and
|
||||
* {@code beta.api.example.com}.
|
||||
*/
|
||||
public boolean hostOnly() {
|
||||
return hostOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cookie's domain. If {@link #hostOnly()} returns true this is the only domain that
|
||||
* matches this cookie; otherwise it matches this domain and all subdomains.
|
||||
*/
|
||||
public String domain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this cookie's path. This cookie matches URLs prefixed with path segments that match
|
||||
* this path's segments. For example, if this path is {@code /foo} this cookie matches requests to
|
||||
* {@code /foo} and {@code /foo/bar}, but not {@code /} or {@code /football}.
|
||||
*/
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this cookie should be limited to only HTTP APIs. In web browsers this prevents
|
||||
* the cookie from being accessible to scripts.
|
||||
*/
|
||||
public boolean httpOnly() {
|
||||
return httpOnly;
|
||||
}
|
||||
|
||||
/** Returns true if this cookie should be limited to only HTTPS requests. */
|
||||
public boolean secure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this cookie should be included on a request to {@code url}. In addition to this
|
||||
* check callers should also confirm that this cookie has not expired.
|
||||
*/
|
||||
public boolean matches(HttpUrl url) {
|
||||
boolean domainMatch = hostOnly
|
||||
? url.host().equals(domain)
|
||||
: domainMatch(url, domain);
|
||||
if (!domainMatch) return false;
|
||||
|
||||
if (!pathMatch(url, path)) return false;
|
||||
|
||||
if (secure && !url.isHttps()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean domainMatch(HttpUrl url, String domain) {
|
||||
String urlHost = url.host();
|
||||
|
||||
if (urlHost.equals(domain)) {
|
||||
return true; // As in 'example.com' matching 'example.com'.
|
||||
}
|
||||
|
||||
if (urlHost.endsWith(domain)
|
||||
&& urlHost.charAt(urlHost.length() - domain.length() - 1) == '.'
|
||||
&& !verifyAsIpAddress(urlHost)) {
|
||||
return true; // As in 'example.com' matching 'www.example.com'.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean pathMatch(HttpUrl url, String path) {
|
||||
String urlPath = url.encodedPath();
|
||||
|
||||
if (urlPath.equals(path)) {
|
||||
return true; // As in '/foo' matching '/foo'.
|
||||
}
|
||||
|
||||
if (urlPath.startsWith(path)) {
|
||||
if (path.endsWith("/")) return true; // As in '/' matching '/foo'.
|
||||
if (urlPath.charAt(path.length()) == '/') return true; // As in '/foo' matching '/foo/bar'.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse a {@code Set-Cookie} HTTP header value {@code setCookie} as a cookie. Returns
|
||||
* null if {@code setCookie} is not a well-formed cookie.
|
||||
*/
|
||||
public static Cookie parse(HttpUrl url, String setCookie) {
|
||||
return parse(System.currentTimeMillis(), url, setCookie);
|
||||
}
|
||||
|
||||
static Cookie parse(long currentTimeMillis, HttpUrl url, String setCookie) {
|
||||
int pos = 0;
|
||||
int limit = setCookie.length();
|
||||
int cookiePairEnd = delimiterOffset(setCookie, pos, limit, ';');
|
||||
|
||||
int pairEqualsSign = delimiterOffset(setCookie, pos, cookiePairEnd, '=');
|
||||
if (pairEqualsSign == cookiePairEnd) return null;
|
||||
|
||||
String cookieName = trimSubstring(setCookie, pos, pairEqualsSign);
|
||||
if (cookieName.isEmpty()) return null;
|
||||
|
||||
String cookieValue = trimSubstring(setCookie, pairEqualsSign + 1, cookiePairEnd);
|
||||
|
||||
long expiresAt = HttpDate.MAX_DATE;
|
||||
long deltaSeconds = -1L;
|
||||
String domain = null;
|
||||
String path = null;
|
||||
boolean secureOnly = false;
|
||||
boolean httpOnly = false;
|
||||
boolean hostOnly = true;
|
||||
boolean persistent = false;
|
||||
|
||||
pos = cookiePairEnd + 1;
|
||||
while (pos < limit) {
|
||||
int attributePairEnd = delimiterOffset(setCookie, pos, limit, ';');
|
||||
|
||||
int attributeEqualsSign = delimiterOffset(setCookie, pos, attributePairEnd, '=');
|
||||
String attributeName = trimSubstring(setCookie, pos, attributeEqualsSign);
|
||||
String attributeValue = attributeEqualsSign < attributePairEnd
|
||||
? trimSubstring(setCookie, attributeEqualsSign + 1, attributePairEnd)
|
||||
: "";
|
||||
|
||||
if (attributeName.equalsIgnoreCase("expires")) {
|
||||
try {
|
||||
expiresAt = parseExpires(attributeValue, 0, attributeValue.length());
|
||||
persistent = true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Ignore this attribute, it isn't recognizable as a date.
|
||||
}
|
||||
} else if (attributeName.equalsIgnoreCase("max-age")) {
|
||||
try {
|
||||
deltaSeconds = parseMaxAge(attributeValue);
|
||||
persistent = true;
|
||||
} catch (NumberFormatException e) {
|
||||
// Ignore this attribute, it isn't recognizable as a max age.
|
||||
}
|
||||
} else if (attributeName.equalsIgnoreCase("domain")) {
|
||||
try {
|
||||
domain = parseDomain(attributeValue);
|
||||
hostOnly = false;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Ignore this attribute, it isn't recognizable as a domain.
|
||||
}
|
||||
} else if (attributeName.equalsIgnoreCase("path")) {
|
||||
path = attributeValue;
|
||||
} else if (attributeName.equalsIgnoreCase("secure")) {
|
||||
secureOnly = true;
|
||||
} else if (attributeName.equalsIgnoreCase("httponly")) {
|
||||
httpOnly = true;
|
||||
}
|
||||
|
||||
pos = attributePairEnd + 1;
|
||||
}
|
||||
|
||||
// If 'Max-Age' is present, it takes precedence over 'Expires', regardless of the order the two
|
||||
// attributes are declared in the cookie string.
|
||||
if (deltaSeconds == Long.MIN_VALUE) {
|
||||
expiresAt = Long.MIN_VALUE;
|
||||
} else if (deltaSeconds != -1L) {
|
||||
long deltaMilliseconds = deltaSeconds <= (Long.MAX_VALUE / 1000)
|
||||
? deltaSeconds * 1000
|
||||
: Long.MAX_VALUE;
|
||||
expiresAt = currentTimeMillis + deltaMilliseconds;
|
||||
if (expiresAt < currentTimeMillis || expiresAt > HttpDate.MAX_DATE) {
|
||||
expiresAt = HttpDate.MAX_DATE; // Handle overflow & limit the date range.
|
||||
}
|
||||
}
|
||||
|
||||
// If the domain is present, it must domain match. Otherwise we have a host-only cookie.
|
||||
if (domain == null) {
|
||||
domain = url.host();
|
||||
} else if (!domainMatch(url, domain)) {
|
||||
return null; // No domain match? This is either incompetence or malice!
|
||||
}
|
||||
|
||||
// If the path is absent or didn't start with '/', use the default path. It's a string like
|
||||
// '/foo/bar' for a URL like 'http://example.com/foo/bar/baz'. It always starts with '/'.
|
||||
if (path == null || !path.startsWith("/")) {
|
||||
String encodedPath = url.encodedPath();
|
||||
int lastSlash = encodedPath.lastIndexOf('/');
|
||||
path = lastSlash != 0 ? encodedPath.substring(0, lastSlash) : "/";
|
||||
}
|
||||
|
||||
return new Cookie(cookieName, cookieValue, expiresAt, domain, path, secureOnly, httpOnly,
|
||||
hostOnly, persistent);
|
||||
}
|
||||
|
||||
/** Parse a date as specified in RFC 6265, section 5.1.1. */
|
||||
private static long parseExpires(String s, int pos, int limit) {
|
||||
pos = dateCharacterOffset(s, pos, limit, false);
|
||||
|
||||
int hour = -1;
|
||||
int minute = -1;
|
||||
int second = -1;
|
||||
int dayOfMonth = -1;
|
||||
int month = -1;
|
||||
int year = -1;
|
||||
Matcher matcher = TIME_PATTERN.matcher(s);
|
||||
|
||||
while (pos < limit) {
|
||||
int end = dateCharacterOffset(s, pos + 1, limit, true);
|
||||
matcher.region(pos, end);
|
||||
|
||||
if (hour == -1 && matcher.usePattern(TIME_PATTERN).matches()) {
|
||||
hour = Integer.parseInt(matcher.group(1));
|
||||
minute = Integer.parseInt(matcher.group(2));
|
||||
second = Integer.parseInt(matcher.group(3));
|
||||
} else if (dayOfMonth == -1 && matcher.usePattern(DAY_OF_MONTH_PATTERN).matches()) {
|
||||
dayOfMonth = Integer.parseInt(matcher.group(1));
|
||||
} else if (month == -1 && matcher.usePattern(MONTH_PATTERN).matches()) {
|
||||
String monthString = matcher.group(1).toLowerCase(Locale.US);
|
||||
month = MONTH_PATTERN.pattern().indexOf(monthString) / 4; // Sneaky! jan=1, dec=12.
|
||||
} else if (year == -1 && matcher.usePattern(YEAR_PATTERN).matches()) {
|
||||
year = Integer.parseInt(matcher.group(1));
|
||||
}
|
||||
|
||||
pos = dateCharacterOffset(s, end + 1, limit, false);
|
||||
}
|
||||
|
||||
// Convert two-digit years into four-digit years. 99 becomes 1999, 15 becomes 2015.
|
||||
if (year >= 70 && year <= 99) year += 1900;
|
||||
if (year >= 0 && year <= 69) year += 2000;
|
||||
|
||||
// If any partial is omitted or out of range, return -1. The date is impossible. Note that leap
|
||||
// seconds are not supported by this syntax.
|
||||
if (year < 1601) throw new IllegalArgumentException();
|
||||
if (month == -1) throw new IllegalArgumentException();
|
||||
if (dayOfMonth < 1 || dayOfMonth > 31) throw new IllegalArgumentException();
|
||||
if (hour < 0 || hour > 23) throw new IllegalArgumentException();
|
||||
if (minute < 0 || minute > 59) throw new IllegalArgumentException();
|
||||
if (second < 0 || second > 59) throw new IllegalArgumentException();
|
||||
|
||||
Calendar calendar = new GregorianCalendar(UTC);
|
||||
calendar.setLenient(false);
|
||||
calendar.set(Calendar.YEAR, year);
|
||||
calendar.set(Calendar.MONTH, month - 1);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
||||
calendar.set(Calendar.MINUTE, minute);
|
||||
calendar.set(Calendar.SECOND, second);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
return calendar.getTimeInMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the next date character in {@code input}, or if {@code invert} the index
|
||||
* of the next non-date character in {@code input}.
|
||||
*/
|
||||
private static int dateCharacterOffset(String input, int pos, int limit, boolean invert) {
|
||||
for (int i = pos; i < limit; i++) {
|
||||
int c = input.charAt(i);
|
||||
boolean dateCharacter = (c < ' ' && c != '\t') || (c >= '\u007f')
|
||||
|| (c >= '0' && c <= '9')
|
||||
|| (c >= 'a' && c <= 'z')
|
||||
|| (c >= 'A' && c <= 'Z')
|
||||
|| (c == ':');
|
||||
if (dateCharacter == !invert) return i;
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the positive value if {@code attributeValue} is positive, or {@link Long#MIN_VALUE} if
|
||||
* it is either 0 or negative. If the value is positive but out of range, this returns {@link
|
||||
* Long#MAX_VALUE}.
|
||||
*
|
||||
* @throws NumberFormatException if {@code s} is not an integer of any precision.
|
||||
*/
|
||||
private static long parseMaxAge(String s) {
|
||||
try {
|
||||
long parsed = Long.parseLong(s);
|
||||
return parsed <= 0L ? Long.MIN_VALUE : parsed;
|
||||
} catch (NumberFormatException e) {
|
||||
// Check if the value is an integer (positive or negative) that's too big for a long.
|
||||
if (s.matches("-?\\d+")) {
|
||||
return s.startsWith("-") ? Long.MIN_VALUE : Long.MAX_VALUE;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a domain string like {@code example.com} for an input domain like {@code EXAMPLE.COM}
|
||||
* or {@code .example.com}.
|
||||
*/
|
||||
private static String parseDomain(String s) {
|
||||
if (s.endsWith(".")) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (s.startsWith(".")) {
|
||||
s = s.substring(1);
|
||||
}
|
||||
String canonicalDomain = domainToAscii(s);
|
||||
if (canonicalDomain == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return canonicalDomain;
|
||||
}
|
||||
|
||||
/** Returns all of the cookies from a set of HTTP response headers. */
|
||||
public static List<Cookie> parseAll(HttpUrl url, Headers headers) {
|
||||
List<String> cookieStrings = headers.values("Set-Cookie");
|
||||
List<Cookie> cookies = null;
|
||||
|
||||
for (int i = 0, size = cookieStrings.size(); i < size; i++) {
|
||||
Cookie cookie = Cookie.parse(url, cookieStrings.get(i));
|
||||
if (cookie == null) continue;
|
||||
if (cookies == null) cookies = new ArrayList<>();
|
||||
cookies.add(cookie);
|
||||
}
|
||||
|
||||
return cookies != null
|
||||
? Collections.unmodifiableList(cookies)
|
||||
: Collections.<Cookie>emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a cookie. The {@linkplain #name() name}, {@linkplain #value() value}, and {@linkplain
|
||||
* #domain() domain} values must all be set before calling {@link #build}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
String name;
|
||||
String value;
|
||||
long expiresAt = HttpDate.MAX_DATE;
|
||||
String domain;
|
||||
String path = "/";
|
||||
boolean secure;
|
||||
boolean httpOnly;
|
||||
boolean persistent;
|
||||
boolean hostOnly;
|
||||
|
||||
public Builder name(String name) {
|
||||
if (name == null) throw new NullPointerException("name == null");
|
||||
if (!name.trim().equals(name)) throw new IllegalArgumentException("name is not trimmed");
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder value(String value) {
|
||||
if (value == null) throw new NullPointerException("value == null");
|
||||
if (!value.trim().equals(value)) throw new IllegalArgumentException("value is not trimmed");
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder expiresAt(long expiresAt) {
|
||||
if (expiresAt <= 0) expiresAt = Long.MIN_VALUE;
|
||||
if (expiresAt > HttpDate.MAX_DATE) expiresAt = HttpDate.MAX_DATE;
|
||||
this.expiresAt = expiresAt;
|
||||
this.persistent = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the domain pattern for this cookie. The cookie will match {@code domain} and all of its
|
||||
* subdomains.
|
||||
*/
|
||||
public Builder domain(String domain) {
|
||||
return domain(domain, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the host-only domain for this cookie. The cookie will match {@code domain} but none of
|
||||
* its subdomains.
|
||||
*/
|
||||
public Builder hostOnlyDomain(String domain) {
|
||||
return domain(domain, true);
|
||||
}
|
||||
|
||||
private Builder domain(String domain, boolean hostOnly) {
|
||||
if (domain == null) throw new IllegalArgumentException("domain == null");
|
||||
String canonicalDomain = Util.domainToAscii(domain);
|
||||
if (canonicalDomain == null) {
|
||||
throw new IllegalArgumentException("unexpected domain: " + domain);
|
||||
}
|
||||
this.domain = canonicalDomain;
|
||||
this.hostOnly = hostOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder path(String path) {
|
||||
if (!path.startsWith("/")) throw new IllegalArgumentException("path must start with '/'");
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder secure() {
|
||||
this.secure = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder httpOnly() {
|
||||
this.httpOnly = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Cookie build() {
|
||||
return new Cookie(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(name);
|
||||
result.append('=');
|
||||
result.append(value);
|
||||
|
||||
if (persistent) {
|
||||
if (expiresAt == Long.MIN_VALUE) {
|
||||
result.append("; max-age=0");
|
||||
} else {
|
||||
result.append("; expires=").append(HttpDate.format(new Date(expiresAt)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!hostOnly) {
|
||||
result.append("; domain=").append(domain);
|
||||
}
|
||||
|
||||
result.append("; path=").append(path);
|
||||
|
||||
if (secure) {
|
||||
result.append("; secure");
|
||||
}
|
||||
|
||||
if (httpOnly) {
|
||||
result.append("; httponly");
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
if (!(other instanceof Cookie)) return false;
|
||||
Cookie that = (Cookie) other;
|
||||
return that.name.equals(name)
|
||||
&& that.value.equals(value)
|
||||
&& that.domain.equals(domain)
|
||||
&& that.path.equals(path)
|
||||
&& that.expiresAt == expiresAt
|
||||
&& that.secure == secure
|
||||
&& that.httpOnly == httpOnly
|
||||
&& that.persistent == persistent
|
||||
&& that.hostOnly == hostOnly;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int hash = 17;
|
||||
hash = 31 * hash + name.hashCode();
|
||||
hash = 31 * hash + value.hashCode();
|
||||
hash = 31 * hash + domain.hashCode();
|
||||
hash = 31 * hash + path.hashCode();
|
||||
hash = 31 * hash + (int) (expiresAt ^ (expiresAt >>> 32));
|
||||
hash = 31 * hash + (secure ? 0 : 1);
|
||||
hash = 31 * hash + (httpOnly ? 0 : 1);
|
||||
hash = 31 * hash + (persistent ? 0 : 1);
|
||||
hash = 31 * hash + (hostOnly ? 0 : 1);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides <strong>policy</strong> and <strong>persistence</strong> for HTTP cookies.
|
||||
*
|
||||
* <p>As policy, implementations of this interface are responsible for selecting which cookies to
|
||||
* accept and which to reject. A reasonable policy is to reject all cookies, though that may be
|
||||
* interfere with session-based authentication schemes that require cookies.
|
||||
*
|
||||
* <p>As persistence, implementations of this interface must also provide storage of cookies. Simple
|
||||
* implementations may store cookies in memory; sophisticated ones may use the file system or
|
||||
* database to hold accepted cookies. The <a
|
||||
* href="https://tools.ietf.org/html/rfc6265#section-5.3">cookie storage model</a> specifies
|
||||
* policies for updating and expiring cookies.
|
||||
*/
|
||||
public interface CookieJar {
|
||||
/** A cookie jar that never accepts any cookies. */
|
||||
CookieJar NO_COOKIES = new CookieJar() {
|
||||
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
|
||||
}
|
||||
|
||||
@Override public List<Cookie> loadForRequest(HttpUrl url, Request request) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves {@code cookies} from an HTTP response to this store according to this jar's policy.
|
||||
*
|
||||
* <p>Note that this method may be called a second time for a single HTTP response if the response
|
||||
* includes a trailer. For this obscure HTTP feature, {@code cookies} contains only the trailer's
|
||||
* cookies.
|
||||
*/
|
||||
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
|
||||
|
||||
/**
|
||||
* Load cookies from the jar for an HTTP request to {@code url}. This method returns a possibly
|
||||
* empty list of cookies for the network request.
|
||||
*
|
||||
* <p>Simple implementations will return the accepted cookies that have not yet expired and that
|
||||
* {@linkplain Cookie#matches match} {@code url}.
|
||||
*/
|
||||
List<Cookie> loadForRequest(HttpUrl url, Request request);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import okio.ByteString;
|
||||
|
||||
/** Factory for HTTP authorization credentials. */
|
||||
public final class Credentials {
|
||||
private Credentials() {
|
||||
}
|
||||
|
||||
/** Returns an auth credential for the Basic scheme. */
|
||||
public static String basic(String userName, String password) {
|
||||
try {
|
||||
String usernameAndPassword = userName + ":" + password;
|
||||
byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
|
||||
String encoded = ByteString.of(bytes).base64();
|
||||
return "Basic " + encoded;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +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 okhttp3;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import okhttp3.RealCall.AsyncCall;
|
||||
import okhttp3.internal.Util;
|
||||
|
||||
/**
|
||||
* Policy on when async requests are executed.
|
||||
*
|
||||
* <p>Each dispatcher uses an {@link ExecutorService} to run calls internally. If you supply your
|
||||
* own executor, it should be able to run {@linkplain #getMaxRequests the configured maximum} number
|
||||
* of calls concurrently.
|
||||
*/
|
||||
public final class Dispatcher {
|
||||
private int maxRequests = 64;
|
||||
private int maxRequestsPerHost = 5;
|
||||
|
||||
/** Executes calls. Created lazily. */
|
||||
private ExecutorService executorService;
|
||||
|
||||
/** Ready async calls in the order they'll be run. */
|
||||
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
|
||||
|
||||
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
|
||||
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
|
||||
|
||||
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
|
||||
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
|
||||
|
||||
public Dispatcher(ExecutorService executorService) {
|
||||
this.executorService = executorService;
|
||||
}
|
||||
|
||||
public Dispatcher() {
|
||||
}
|
||||
|
||||
public synchronized ExecutorService executorService() {
|
||||
if (executorService == null) {
|
||||
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
|
||||
}
|
||||
return executorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of requests to execute concurrently. Above this requests queue in
|
||||
* memory, waiting for the running calls to complete.
|
||||
*
|
||||
* <p>If more than {@code maxRequests} requests are in flight when this is invoked, those requests
|
||||
* will remain in flight.
|
||||
*/
|
||||
public synchronized void setMaxRequests(int maxRequests) {
|
||||
if (maxRequests < 1) {
|
||||
throw new IllegalArgumentException("max < 1: " + maxRequests);
|
||||
}
|
||||
this.maxRequests = maxRequests;
|
||||
promoteCalls();
|
||||
}
|
||||
|
||||
public synchronized int getMaxRequests() {
|
||||
return maxRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of requests for each host to execute concurrently. This limits requests
|
||||
* by the URL's host name. Note that concurrent requests to a single IP address may still exceed
|
||||
* this limit: multiple hostnames may share an IP address or be routed through the same HTTP
|
||||
* proxy.
|
||||
*
|
||||
* <p>If more than {@code maxRequestsPerHost} requests are in flight when this is invoked, those
|
||||
* requests will remain in flight.
|
||||
*/
|
||||
public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
|
||||
if (maxRequestsPerHost < 1) {
|
||||
throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
|
||||
}
|
||||
this.maxRequestsPerHost = maxRequestsPerHost;
|
||||
promoteCalls();
|
||||
}
|
||||
|
||||
public synchronized int getMaxRequestsPerHost() {
|
||||
return maxRequestsPerHost;
|
||||
}
|
||||
|
||||
synchronized void enqueue(AsyncCall call) {
|
||||
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
|
||||
runningAsyncCalls.add(call);
|
||||
executorService().execute(call);
|
||||
} else {
|
||||
readyAsyncCalls.add(call);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all calls currently enqueued or executing. Includes calls executed both {@linkplain
|
||||
* Call#execute() synchronously} and {@linkplain Call#enqueue asynchronously}.
|
||||
*/
|
||||
public synchronized void cancelAll() {
|
||||
for (AsyncCall call : readyAsyncCalls) {
|
||||
call.cancel();
|
||||
}
|
||||
|
||||
for (AsyncCall call : runningAsyncCalls) {
|
||||
call.cancel();
|
||||
}
|
||||
|
||||
for (RealCall call : runningSyncCalls) {
|
||||
call.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/** Used by {@code AsyncCall#run} to signal completion. */
|
||||
synchronized void finished(AsyncCall call) {
|
||||
if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
|
||||
promoteCalls();
|
||||
}
|
||||
|
||||
private void promoteCalls() {
|
||||
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
|
||||
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
|
||||
|
||||
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
|
||||
AsyncCall call = i.next();
|
||||
|
||||
if (runningCallsForHost(call) < maxRequestsPerHost) {
|
||||
i.remove();
|
||||
runningAsyncCalls.add(call);
|
||||
executorService().execute(call);
|
||||
}
|
||||
|
||||
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the number of running calls that share a host with {@code call}. */
|
||||
private int runningCallsForHost(AsyncCall call) {
|
||||
int result = 0;
|
||||
for (AsyncCall c : runningAsyncCalls) {
|
||||
if (c.host().equals(call.host())) result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Used by {@code Call#execute} to signal it is in-flight. */
|
||||
synchronized void executed(RealCall call) {
|
||||
runningSyncCalls.add(call);
|
||||
}
|
||||
|
||||
/** Used by {@code Call#execute} to signal completion. */
|
||||
synchronized void finished(Call call) {
|
||||
if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
|
||||
}
|
||||
|
||||
/** Returns a snapshot of the calls currently awaiting execution. */
|
||||
public synchronized List<Call> queuedCalls() {
|
||||
List<Call> result = new ArrayList<>();
|
||||
for (AsyncCall asyncCall : readyAsyncCalls) {
|
||||
result.add(asyncCall.get());
|
||||
}
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
/** Returns a snapshot of the calls currently being executed. */
|
||||
public synchronized List<Call> runningCalls() {
|
||||
List<Call> result = new ArrayList<>();
|
||||
result.addAll(runningSyncCalls);
|
||||
for (AsyncCall asyncCall : runningAsyncCalls) {
|
||||
result.add(asyncCall.get());
|
||||
}
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
public synchronized int queuedCallsCount() {
|
||||
return readyAsyncCalls.size();
|
||||
}
|
||||
|
||||
public synchronized int runningCallsCount() {
|
||||
return runningAsyncCalls.size() + runningSyncCalls.size();
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A domain name service that resolves IP addresses for host names. Most applications will use the
|
||||
* {@linkplain #SYSTEM system DNS service}, which is the default. Some applications may provide
|
||||
* their own implementation to use a different DNS server, to prefer IPv6 addresses, to prefer IPv4
|
||||
* addresses, or to force a specific known IP address.
|
||||
*
|
||||
* <p>Implementations of this interface must be safe for concurrent use.
|
||||
*/
|
||||
public interface Dns {
|
||||
/**
|
||||
* A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to
|
||||
* lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance.
|
||||
*/
|
||||
Dns SYSTEM = new Dns() {
|
||||
@Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
|
||||
if (hostname == null) throw new UnknownHostException("hostname == null");
|
||||
return Arrays.asList(InetAddress.getAllByName(hostname));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the IP addresses of {@code hostname}, in the order they will be attempted by OkHttp. If
|
||||
* a connection to an address fails, OkHttp will retry the connection with the next address until
|
||||
* either a connection is made, the set of IP addresses is exhausted, or a limit is exceeded.
|
||||
*/
|
||||
List<InetAddress> lookup(String hostname) throws UnknownHostException;
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import okhttp3.internal.Util;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSink;
|
||||
|
||||
import static okhttp3.HttpUrl.FORM_ENCODE_SET;
|
||||
import static okhttp3.HttpUrl.percentDecode;
|
||||
|
||||
public final class FormBody extends RequestBody {
|
||||
private static final MediaType CONTENT_TYPE =
|
||||
MediaType.parse("application/x-www-form-urlencoded");
|
||||
|
||||
private final List<String> encodedNames;
|
||||
private final List<String> encodedValues;
|
||||
|
||||
private FormBody(List<String> encodedNames, List<String> encodedValues) {
|
||||
this.encodedNames = Util.immutableList(encodedNames);
|
||||
this.encodedValues = Util.immutableList(encodedValues);
|
||||
}
|
||||
|
||||
/** The number of key-value pairs in this form-encoded body. */
|
||||
public int size() {
|
||||
return encodedNames.size();
|
||||
}
|
||||
|
||||
public String encodedName(int index) {
|
||||
return encodedNames.get(index);
|
||||
}
|
||||
|
||||
public String name(int index) {
|
||||
return percentDecode(encodedName(index), true);
|
||||
}
|
||||
|
||||
public String encodedValue(int index) {
|
||||
return encodedValues.get(index);
|
||||
}
|
||||
|
||||
public String value(int index) {
|
||||
return percentDecode(encodedValue(index), true);
|
||||
}
|
||||
|
||||
@Override public MediaType contentType() {
|
||||
return CONTENT_TYPE;
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return writeOrCountBytes(null, true);
|
||||
}
|
||||
|
||||
@Override public void writeTo(BufferedSink sink) throws IOException {
|
||||
writeOrCountBytes(sink, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Either writes this request to {@code sink} or measures its content length. We have one method
|
||||
* do double-duty to make sure the counting and content are consistent, particularly when it comes
|
||||
* to awkward operations like measuring the encoded length of header strings, or the
|
||||
* length-in-digits of an encoded integer.
|
||||
*/
|
||||
private long writeOrCountBytes(BufferedSink sink, boolean countBytes) {
|
||||
long byteCount = 0L;
|
||||
|
||||
Buffer buffer;
|
||||
if (countBytes) {
|
||||
buffer = new Buffer();
|
||||
} else {
|
||||
buffer = sink.buffer();
|
||||
}
|
||||
|
||||
for (int i = 0, size = encodedNames.size(); i < size; i++) {
|
||||
if (i > 0) buffer.writeByte('&');
|
||||
buffer.writeUtf8(encodedNames.get(i));
|
||||
buffer.writeByte('=');
|
||||
buffer.writeUtf8(encodedValues.get(i));
|
||||
}
|
||||
|
||||
if (countBytes) {
|
||||
byteCount = buffer.size();
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
return byteCount;
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final List<String> names = new ArrayList<>();
|
||||
private final List<String> values = new ArrayList<>();
|
||||
|
||||
public Builder add(String name, String value) {
|
||||
names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, false, false, true, true));
|
||||
values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, false, false, true, true));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addEncoded(String name, String value) {
|
||||
names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, true, false, true, true));
|
||||
values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, true, false, true, true));
|
||||
return this;
|
||||
}
|
||||
|
||||
public FormBody build() {
|
||||
return new FormBody(names, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +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 okhttp3;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import okhttp3.internal.Util;
|
||||
|
||||
/**
|
||||
* A record of a TLS handshake. For HTTPS clients, the client is <i>local</i> and the remote server
|
||||
* is its <i>peer</i>.
|
||||
*
|
||||
* <p>This value object describes a completed handshake. Use {@link ConnectionSpec} to set policy
|
||||
* for new handshakes.
|
||||
*/
|
||||
public final class Handshake {
|
||||
private final TlsVersion tlsVersion;
|
||||
private final CipherSuite cipherSuite;
|
||||
private final List<Certificate> peerCertificates;
|
||||
private final List<Certificate> localCertificates;
|
||||
|
||||
private Handshake(TlsVersion tlsVersion, CipherSuite cipherSuite,
|
||||
List<Certificate> peerCertificates, List<Certificate> localCertificates) {
|
||||
this.tlsVersion = tlsVersion;
|
||||
this.cipherSuite = cipherSuite;
|
||||
this.peerCertificates = peerCertificates;
|
||||
this.localCertificates = localCertificates;
|
||||
}
|
||||
|
||||
public static Handshake get(SSLSession session) {
|
||||
String cipherSuiteString = session.getCipherSuite();
|
||||
if (cipherSuiteString == null) throw new IllegalStateException("cipherSuite == null");
|
||||
CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString);
|
||||
|
||||
String tlsVersionString = session.getProtocol();
|
||||
if (tlsVersionString == null) throw new IllegalStateException("tlsVersion == null");
|
||||
TlsVersion tlsVersion = TlsVersion.forJavaName(tlsVersionString);
|
||||
|
||||
Certificate[] peerCertificates;
|
||||
try {
|
||||
peerCertificates = session.getPeerCertificates();
|
||||
} catch (SSLPeerUnverifiedException ignored) {
|
||||
peerCertificates = null;
|
||||
}
|
||||
List<Certificate> peerCertificatesList = peerCertificates != null
|
||||
? Util.immutableList(peerCertificates)
|
||||
: Collections.<Certificate>emptyList();
|
||||
|
||||
Certificate[] localCertificates = session.getLocalCertificates();
|
||||
List<Certificate> localCertificatesList = localCertificates != null
|
||||
? Util.immutableList(localCertificates)
|
||||
: Collections.<Certificate>emptyList();
|
||||
|
||||
return new Handshake(tlsVersion, cipherSuite, peerCertificatesList, localCertificatesList);
|
||||
}
|
||||
|
||||
public static Handshake get(TlsVersion tlsVersion, CipherSuite cipherSuite,
|
||||
List<Certificate> peerCertificates, List<Certificate> localCertificates) {
|
||||
if (cipherSuite == null) throw new IllegalArgumentException("cipherSuite == null");
|
||||
return new Handshake(tlsVersion, cipherSuite, Util.immutableList(peerCertificates),
|
||||
Util.immutableList(localCertificates));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TLS version used for this connection. May return null if the response was cached
|
||||
* with a version of OkHttp prior to 3.0.
|
||||
*/
|
||||
public TlsVersion tlsVersion() {
|
||||
return tlsVersion;
|
||||
}
|
||||
|
||||
/** Returns the cipher suite used for the connection. */
|
||||
public CipherSuite cipherSuite() {
|
||||
return cipherSuite;
|
||||
}
|
||||
|
||||
/** Returns a possibly-empty list of certificates that identify the remote peer. */
|
||||
public List<Certificate> peerCertificates() {
|
||||
return peerCertificates;
|
||||
}
|
||||
|
||||
/** Returns the remote peer's principle, or null if that peer is anonymous. */
|
||||
public Principal peerPrincipal() {
|
||||
return !peerCertificates.isEmpty()
|
||||
? ((X509Certificate) peerCertificates.get(0)).getSubjectX500Principal()
|
||||
: null;
|
||||
}
|
||||
|
||||
/** Returns a possibly-empty list of certificates that identify this peer. */
|
||||
public List<Certificate> localCertificates() {
|
||||
return localCertificates;
|
||||
}
|
||||
|
||||
/** Returns the local principle, or null if this peer is anonymous. */
|
||||
public Principal localPrincipal() {
|
||||
return !localCertificates.isEmpty()
|
||||
? ((X509Certificate) localCertificates.get(0)).getSubjectX500Principal()
|
||||
: null;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
if (!(other instanceof Handshake)) return false;
|
||||
Handshake that = (Handshake) other;
|
||||
return Util.equal(cipherSuite, that.cipherSuite)
|
||||
&& cipherSuite.equals(that.cipherSuite)
|
||||
&& peerCertificates.equals(that.peerCertificates)
|
||||
&& localCertificates.equals(that.localCertificates);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + (tlsVersion != null ? tlsVersion.hashCode() : 0);
|
||||
result = 31 * result + cipherSuite.hashCode();
|
||||
result = 31 * result + peerCertificates.hashCode();
|
||||
result = 31 * result + localCertificates.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
/*
|
||||
* 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 okhttp3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import okhttp3.internal.http.HttpDate;
|
||||
|
||||
/**
|
||||
* The header fields of a single HTTP message. Values are uninterpreted strings; use {@code Request}
|
||||
* and {@code Response} for interpreted headers. This class maintains the order of the header fields
|
||||
* within the HTTP message.
|
||||
*
|
||||
* <p>This class tracks header values line-by-line. A field with multiple comma- separated values on
|
||||
* the same line will be treated as a field with a single value by this class. It is the caller's
|
||||
* responsibility to detect and split on commas if their field permits multiple values. This
|
||||
* simplifies use of single-valued fields whose values routinely contain commas, such as cookies or
|
||||
* dates.
|
||||
*
|
||||
* <p>This class trims whitespace from values. It never returns values with leading or trailing
|
||||
* whitespace.
|
||||
*
|
||||
* <p>Instances of this class are immutable. Use {@link Builder} to create instances.
|
||||
*/
|
||||
public final class Headers {
|
||||
private final String[] namesAndValues;
|
||||
|
||||
private Headers(Builder builder) {
|
||||
this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
|
||||
}
|
||||
|
||||
private Headers(String[] namesAndValues) {
|
||||
this.namesAndValues = namesAndValues;
|
||||
}
|
||||
|
||||
/** Returns the last value corresponding to the specified field, or null. */
|
||||
public String get(String name) {
|
||||
return get(namesAndValues, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last value corresponding to the specified field parsed as an HTTP date, or null if
|
||||
* either the field is absent or cannot be parsed as a date.
|
||||
*/
|
||||
public Date getDate(String name) {
|
||||
String value = get(name);
|
||||
return value != null ? HttpDate.parse(value) : null;
|
||||
}
|
||||
|
||||
/** Returns the number of field values. */
|
||||
public int size() {
|
||||
return namesAndValues.length / 2;
|
||||
}
|
||||
|
||||
/** Returns the field at {@code position}. */
|
||||
public String name(int index) {
|
||||
return namesAndValues[index * 2];
|
||||
}
|
||||
|
||||
/** Returns the value at {@code index}. */
|
||||
public String value(int index) {
|
||||
return namesAndValues[index * 2 + 1];
|
||||
}
|
||||
|
||||
/** Returns an immutable case-insensitive set of header names. */
|
||||
public Set<String> names() {
|
||||
TreeSet<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (int i = 0, size = size(); i < size; i++) {
|
||||
result.add(name(i));
|
||||
}
|
||||
return Collections.unmodifiableSet(result);
|
||||
}
|
||||
|
||||
/** 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, size = size(); i < size; i++) {
|
||||
if (name.equalsIgnoreCase(name(i))) {
|
||||
if (result == null) result = new ArrayList<>(2);
|
||||
result.add(value(i));
|
||||
}
|
||||
}
|
||||
return result != null
|
||||
? Collections.unmodifiableList(result)
|
||||
: Collections.<String>emptyList();
|
||||
}
|
||||
|
||||
public Builder newBuilder() {
|
||||
Builder result = new Builder();
|
||||
Collections.addAll(result.namesAndValues, namesAndValues);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0, size = size(); i < size; i++) {
|
||||
result.append(name(i)).append(": ").append(value(i)).append("\n");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public Map<String, List<String>> toMultimap() {
|
||||
Map<String, List<String>> result = new LinkedHashMap<String, List<String>>();
|
||||
for (int i = 0, size = size(); i < size; i++) {
|
||||
String name = name(i);
|
||||
List<String> values = result.get(name);
|
||||
if (values == null) {
|
||||
values = new ArrayList<>(2);
|
||||
result.put(name, values);
|
||||
}
|
||||
values.add(value(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String get(String[] namesAndValues, String name) {
|
||||
for (int i = namesAndValues.length - 2; i >= 0; i -= 2) {
|
||||
if (name.equalsIgnoreCase(namesAndValues[i])) {
|
||||
return namesAndValues[i + 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns headers for the alternating header names and values. There must be an even number of
|
||||
* arguments, and they must alternate between header names and values.
|
||||
*/
|
||||
public static Headers of(String... namesAndValues) {
|
||||
if (namesAndValues == null || namesAndValues.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("Expected alternating header names and values");
|
||||
}
|
||||
|
||||
// Make a defensive copy and clean it up.
|
||||
namesAndValues = namesAndValues.clone();
|
||||
for (int i = 0; i < namesAndValues.length; i++) {
|
||||
if (namesAndValues[i] == null) throw new IllegalArgumentException("Headers cannot be null");
|
||||
namesAndValues[i] = namesAndValues[i].trim();
|
||||
}
|
||||
|
||||
// Check for malformed headers.
|
||||
for (int i = 0; i < namesAndValues.length; i += 2) {
|
||||
String name = namesAndValues[i];
|
||||
String value = namesAndValues[i + 1];
|
||||
if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) {
|
||||
throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
|
||||
}
|
||||
}
|
||||
|
||||
return new Headers(namesAndValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns headers for the header names and values in the {@link Map}.
|
||||
*/
|
||||
public static Headers of(Map<String, String> headers) {
|
||||
if (headers == null) {
|
||||
throw new IllegalArgumentException("Expected map with header names and values");
|
||||
}
|
||||
|
||||
// Make a defensive copy and clean it up.
|
||||
String[] namesAndValues = new String[headers.size() * 2];
|
||||
int i = 0;
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
if (header.getKey() == null || header.getValue() == null) {
|
||||
throw new IllegalArgumentException("Headers cannot be null");
|
||||
}
|
||||
String name = header.getKey().trim();
|
||||
String value = header.getValue().trim();
|
||||
if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) {
|
||||
throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
|
||||
}
|
||||
namesAndValues[i] = name;
|
||||
namesAndValues[i + 1] = value;
|
||||
i += 2;
|
||||
}
|
||||
|
||||
return new Headers(namesAndValues);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final List<String> namesAndValues = new ArrayList<>(20);
|
||||
|
||||
/**
|
||||
* Add a header line without any validation. Only appropriate for headers from the remote peer
|
||||
* or cache.
|
||||
*/
|
||||
Builder addLenient(String line) {
|
||||
int index = line.indexOf(":", 1);
|
||||
if (index != -1) {
|
||||
return addLenient(line.substring(0, index), line.substring(index + 1));
|
||||
} else if (line.startsWith(":")) {
|
||||
// Work around empty header names and header names that start with a
|
||||
// colon (created by old broken SPDY versions of the response cache).
|
||||
return addLenient("", line.substring(1)); // Empty header name.
|
||||
} else {
|
||||
return addLenient("", line); // No header name.
|
||||
}
|
||||
}
|
||||
|
||||
/** Add an header line containing a field name, a literal colon, and a value. */
|
||||
public Builder add(String line) {
|
||||
int index = line.indexOf(":");
|
||||
if (index == -1) {
|
||||
throw new IllegalArgumentException("Unexpected header: " + line);
|
||||
}
|
||||
return add(line.substring(0, index).trim(), line.substring(index + 1));
|
||||
}
|
||||
|
||||
/** Add a field with the specified value. */
|
||||
public Builder add(String name, String value) {
|
||||
checkNameAndValue(name, value);
|
||||
return addLenient(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field with the specified value without any validation. Only appropriate for headers
|
||||
* from the remote peer or cache.
|
||||
*/
|
||||
Builder addLenient(String name, String value) {
|
||||
namesAndValues.add(name);
|
||||
namesAndValues.add(value.trim());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder removeAll(String name) {
|
||||
for (int i = 0; i < namesAndValues.size(); i += 2) {
|
||||
if (name.equalsIgnoreCase(namesAndValues.get(i))) {
|
||||
namesAndValues.remove(i); // name
|
||||
namesAndValues.remove(i); // value
|
||||
i -= 2;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a field with the specified value. If the field is not found, it is added. If the field is
|
||||
* found, the existing values are replaced.
|
||||
*/
|
||||
public Builder set(String name, String value) {
|
||||
checkNameAndValue(name, value);
|
||||
removeAll(name);
|
||||
addLenient(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void checkNameAndValue(String name, String value) {
|
||||
if (name == null) throw new IllegalArgumentException("name == null");
|
||||
if (name.isEmpty()) throw new IllegalArgumentException("name is empty");
|
||||
for (int i = 0, length = name.length(); i < length; i++) {
|
||||
char c = name.charAt(i);
|
||||
if (c <= '\u001f' || c >= '\u007f') {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unexpected char %#04x at %d in header name: %s", (int) c, i, name));
|
||||
}
|
||||
}
|
||||
if (value == null) throw new IllegalArgumentException("value == null");
|
||||
for (int i = 0, length = value.length(); i < length; i++) {
|
||||
char c = value.charAt(i);
|
||||
if (c <= '\u001f' || c >= '\u007f') {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Equivalent to {@code build().get(name)}, but potentially faster. */
|
||||
public String get(String name) {
|
||||
for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
|
||||
if (name.equalsIgnoreCase(namesAndValues.get(i))) {
|
||||
return namesAndValues.get(i + 1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Headers build() {
|
||||
return new Headers(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
|
||||
* requests coming back in. Typically interceptors will be used to add, remove, or transform headers
|
||||
* on the request or response.
|
||||
*/
|
||||
public interface Interceptor {
|
||||
Response intercept(Chain chain) throws IOException;
|
||||
|
||||
interface Chain {
|
||||
Request request();
|
||||
|
||||
Response proceed(Request request) throws IOException;
|
||||
|
||||
Connection connection();
|
||||
}
|
||||
}
|
||||
@@ -1,121 +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 okhttp3;
|
||||
|
||||
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;
|
||||
String charsetParameter = parameter.group(2) != null
|
||||
? parameter.group(2) // Value is a token.
|
||||
: parameter.group(3); // Value is a quoted string.
|
||||
if (charset != null && !charsetParameter.equalsIgnoreCase(charset)) {
|
||||
throw new IllegalArgumentException("Multiple different charsets: " + string);
|
||||
}
|
||||
charset = charsetParameter;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import okhttp3.internal.Util;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSink;
|
||||
import okio.ByteString;
|
||||
|
||||
/** An <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC 2387</a>-compliant request body. */
|
||||
public final class MultipartBody extends RequestBody {
|
||||
/**
|
||||
* The "mixed" subtype of "multipart" is intended for use when the body parts are independent and
|
||||
* need to be bundled in a particular order. Any "multipart" subtypes that an implementation does
|
||||
* not recognize must be treated as being of subtype "mixed".
|
||||
*/
|
||||
public static final MediaType MIXED = MediaType.parse("multipart/mixed");
|
||||
|
||||
/**
|
||||
* The "multipart/alternative" type is syntactically identical to "multipart/mixed", but the
|
||||
* semantics are different. In particular, each of the body parts is an "alternative" version of
|
||||
* the same information.
|
||||
*/
|
||||
public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative");
|
||||
|
||||
/**
|
||||
* This type is syntactically identical to "multipart/mixed", but the semantics are different. In
|
||||
* particular, in a digest, the default {@code Content-Type} value for a body part is changed from
|
||||
* "text/plain" to "message/rfc822".
|
||||
*/
|
||||
public static final MediaType DIGEST = MediaType.parse("multipart/digest");
|
||||
|
||||
/**
|
||||
* This type is syntactically identical to "multipart/mixed", but the semantics are different. In
|
||||
* particular, in a parallel entity, the order of body parts is not significant.
|
||||
*/
|
||||
public static final MediaType PARALLEL = MediaType.parse("multipart/parallel");
|
||||
|
||||
/**
|
||||
* The media-type multipart/form-data follows the rules of all multipart MIME data streams as
|
||||
* outlined in RFC 2046. In forms, there are a series of fields to be supplied by the user who
|
||||
* fills out the form. Each field has a name. Within a given form, the names are unique.
|
||||
*/
|
||||
public static final MediaType FORM = MediaType.parse("multipart/form-data");
|
||||
|
||||
private static final byte[] COLONSPACE = {':', ' '};
|
||||
private static final byte[] CRLF = {'\r', '\n'};
|
||||
private static final byte[] DASHDASH = {'-', '-'};
|
||||
|
||||
private final ByteString boundary;
|
||||
private final MediaType originalType;
|
||||
private final MediaType contentType;
|
||||
private final List<Part> parts;
|
||||
private long contentLength = -1L;
|
||||
|
||||
MultipartBody(ByteString boundary, MediaType type, List<Part> parts) {
|
||||
this.boundary = boundary;
|
||||
this.originalType = type;
|
||||
this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
|
||||
this.parts = Util.immutableList(parts);
|
||||
}
|
||||
|
||||
public MediaType type() {
|
||||
return originalType;
|
||||
}
|
||||
|
||||
public String boundary() {
|
||||
return boundary.utf8();
|
||||
}
|
||||
|
||||
/** The number of parts in this multipart body. */
|
||||
public int size() {
|
||||
return parts.size();
|
||||
}
|
||||
|
||||
public List<Part> parts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
public Part part(int index) {
|
||||
return parts.get(index);
|
||||
}
|
||||
|
||||
/** A combination of {@link #type()} and {@link #boundary()}. */
|
||||
@Override public MediaType contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override public long contentLength() throws IOException {
|
||||
long result = contentLength;
|
||||
if (result != -1L) return result;
|
||||
return contentLength = writeOrCountBytes(null, true);
|
||||
}
|
||||
|
||||
@Override public void writeTo(BufferedSink sink) throws IOException {
|
||||
writeOrCountBytes(sink, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Either writes this request to {@code sink} or measures its content length. We have one method
|
||||
* do double-duty to make sure the counting and content are consistent, particularly when it comes
|
||||
* to awkward operations like measuring the encoded length of header strings, or the
|
||||
* length-in-digits of an encoded integer.
|
||||
*/
|
||||
private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {
|
||||
long byteCount = 0L;
|
||||
|
||||
Buffer byteCountBuffer = null;
|
||||
if (countBytes) {
|
||||
sink = byteCountBuffer = new Buffer();
|
||||
}
|
||||
|
||||
for (int p = 0, partCount = parts.size(); p < partCount; p++) {
|
||||
Part part = parts.get(p);
|
||||
Headers headers = part.headers;
|
||||
RequestBody body = part.body;
|
||||
|
||||
sink.write(DASHDASH);
|
||||
sink.write(boundary);
|
||||
sink.write(CRLF);
|
||||
|
||||
if (headers != null) {
|
||||
for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
|
||||
sink.writeUtf8(headers.name(h))
|
||||
.write(COLONSPACE)
|
||||
.writeUtf8(headers.value(h))
|
||||
.write(CRLF);
|
||||
}
|
||||
}
|
||||
|
||||
MediaType contentType = body.contentType();
|
||||
if (contentType != null) {
|
||||
sink.writeUtf8("Content-Type: ")
|
||||
.writeUtf8(contentType.toString())
|
||||
.write(CRLF);
|
||||
}
|
||||
|
||||
long contentLength = body.contentLength();
|
||||
if (contentLength != -1) {
|
||||
sink.writeUtf8("Content-Length: ")
|
||||
.writeDecimalLong(contentLength)
|
||||
.write(CRLF);
|
||||
} else if (countBytes) {
|
||||
// We can't measure the body's size without the sizes of its components.
|
||||
byteCountBuffer.clear();
|
||||
return -1L;
|
||||
}
|
||||
|
||||
sink.write(CRLF);
|
||||
|
||||
if (countBytes) {
|
||||
byteCount += contentLength;
|
||||
} else {
|
||||
body.writeTo(sink);
|
||||
}
|
||||
|
||||
sink.write(CRLF);
|
||||
}
|
||||
|
||||
sink.write(DASHDASH);
|
||||
sink.write(boundary);
|
||||
sink.write(DASHDASH);
|
||||
sink.write(CRLF);
|
||||
|
||||
if (countBytes) {
|
||||
byteCount += byteCountBuffer.size();
|
||||
byteCountBuffer.clear();
|
||||
}
|
||||
|
||||
return byteCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a quoted-string to a StringBuilder.
|
||||
*
|
||||
* <p>RFC 2388 is rather vague about how one should escape special characters in form-data
|
||||
* parameters, and as it turns out Firefox and Chrome actually do rather different things, and
|
||||
* both say in their comments that they're not really sure what the right approach is. We go with
|
||||
* Chrome's behavior (which also experimentally seems to match what IE does), but if you actually
|
||||
* want to have a good chance of things working, please avoid double-quotes, newlines, percent
|
||||
* signs, and the like in your field names.
|
||||
*/
|
||||
static StringBuilder appendQuotedString(StringBuilder target, String key) {
|
||||
target.append('"');
|
||||
for (int i = 0, len = key.length(); i < len; i++) {
|
||||
char ch = key.charAt(i);
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
target.append("%0A");
|
||||
break;
|
||||
case '\r':
|
||||
target.append("%0D");
|
||||
break;
|
||||
case '"':
|
||||
target.append("%22");
|
||||
break;
|
||||
default:
|
||||
target.append(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
target.append('"');
|
||||
return target;
|
||||
}
|
||||
|
||||
public static final class Part {
|
||||
public static Part create(RequestBody body) {
|
||||
return create(null, body);
|
||||
}
|
||||
|
||||
public static Part create(Headers headers, RequestBody body) {
|
||||
if (body == null) {
|
||||
throw new NullPointerException("body == null");
|
||||
}
|
||||
if (headers != null && headers.get("Content-Type") != null) {
|
||||
throw new IllegalArgumentException("Unexpected header: Content-Type");
|
||||
}
|
||||
if (headers != null && headers.get("Content-Length") != null) {
|
||||
throw new IllegalArgumentException("Unexpected header: Content-Length");
|
||||
}
|
||||
return new Part(headers, body);
|
||||
}
|
||||
|
||||
public static Part createFormData(String name, String value) {
|
||||
return createFormData(name, null, RequestBody.create(null, value));
|
||||
}
|
||||
|
||||
public static Part createFormData(String name, String filename, RequestBody body) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("name == null");
|
||||
}
|
||||
StringBuilder disposition = new StringBuilder("form-data; name=");
|
||||
appendQuotedString(disposition, name);
|
||||
|
||||
if (filename != null) {
|
||||
disposition.append("; filename=");
|
||||
appendQuotedString(disposition, filename);
|
||||
}
|
||||
|
||||
return create(Headers.of("Content-Disposition", disposition.toString()), body);
|
||||
}
|
||||
|
||||
private final Headers headers;
|
||||
private final RequestBody body;
|
||||
|
||||
private Part(Headers headers, RequestBody body) {
|
||||
this.headers = headers;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final ByteString boundary;
|
||||
private MediaType type = MIXED;
|
||||
private final List<Part> parts = new ArrayList<>();
|
||||
|
||||
public Builder() {
|
||||
this(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public Builder(String boundary) {
|
||||
this.boundary = ByteString.encodeUtf8(boundary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the MIME type. Expected values for {@code type} are {@link #MIXED} (the default), {@link
|
||||
* #ALTERNATIVE}, {@link #DIGEST}, {@link #PARALLEL} and {@link #FORM}.
|
||||
*/
|
||||
public Builder setType(MediaType type) {
|
||||
if (type == null) {
|
||||
throw new NullPointerException("type == null");
|
||||
}
|
||||
if (!type.type().equals("multipart")) {
|
||||
throw new IllegalArgumentException("multipart != " + type);
|
||||
}
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Add a part to the body. */
|
||||
public Builder addPart(RequestBody body) {
|
||||
return addPart(Part.create(body));
|
||||
}
|
||||
|
||||
/** Add a part to the body. */
|
||||
public Builder addPart(Headers headers, RequestBody body) {
|
||||
return addPart(Part.create(headers, body));
|
||||
}
|
||||
|
||||
/** Add a form data part to the body. */
|
||||
public Builder addFormDataPart(String name, String value) {
|
||||
return addPart(Part.createFormData(name, value));
|
||||
}
|
||||
|
||||
/** Add a form data part to the body. */
|
||||
public Builder addFormDataPart(String name, String filename, RequestBody body) {
|
||||
return addPart(Part.createFormData(name, filename, body));
|
||||
}
|
||||
|
||||
/** Add a part to the body. */
|
||||
public Builder addPart(Part part) {
|
||||
if (part == null) throw new NullPointerException("part == null");
|
||||
parts.add(part);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Assemble the specified parts into a request body. */
|
||||
public MultipartBody build() {
|
||||
if (parts.isEmpty()) {
|
||||
throw new IllegalStateException("Multipart body must have at least one part.");
|
||||
}
|
||||
return new MultipartBody(boundary, type, parts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,721 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import okhttp3.internal.Internal;
|
||||
import okhttp3.internal.InternalCache;
|
||||
import okhttp3.internal.Platform;
|
||||
import okhttp3.internal.RouteDatabase;
|
||||
import okhttp3.internal.Util;
|
||||
import okhttp3.internal.http.StreamAllocation;
|
||||
import okhttp3.internal.io.RealConnection;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
import okhttp3.internal.tls.TrustRootIndex;
|
||||
|
||||
/**
|
||||
* Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their
|
||||
* responses. Most applications can use a single OkHttpClient for all of their HTTP requests,
|
||||
* benefiting from a shared response cache, thread pool, connection re-use, etc.
|
||||
*
|
||||
* <p>To create an {@code OkHttpClient} with the default settings, use the {@linkplain
|
||||
* #OkHttpClient() default constructor}. Or create a configured instance with {@link
|
||||
* OkHttpClient.Builder}. To adjust an existing client before making a request, use {@link
|
||||
* #newBuilder()}. This example shows a call with a 30 second timeout:
|
||||
* <pre> {@code
|
||||
*
|
||||
* OkHttpClient client = ...
|
||||
* OkHttpClient clientWith30sTimeout = client.newBuilder()
|
||||
* .readTimeout(30, TimeUnit.SECONDS)
|
||||
* .build();
|
||||
* Response response = clientWith30sTimeout.newCall(request).execute();
|
||||
* }</pre>
|
||||
*/
|
||||
public class OkHttpClient implements Cloneable, Call.Factory {
|
||||
private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
|
||||
Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
|
||||
|
||||
private static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
|
||||
ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT);
|
||||
|
||||
static {
|
||||
Internal.instance = new Internal() {
|
||||
@Override public void addLenient(Headers.Builder builder, String line) {
|
||||
builder.addLenient(line);
|
||||
}
|
||||
|
||||
@Override public void addLenient(Headers.Builder builder, String name, String value) {
|
||||
builder.addLenient(name, value);
|
||||
}
|
||||
|
||||
@Override public void setCache(OkHttpClient.Builder builder, InternalCache internalCache) {
|
||||
builder.setInternalCache(internalCache);
|
||||
}
|
||||
|
||||
@Override public InternalCache internalCache(OkHttpClient client) {
|
||||
return client.internalCache();
|
||||
}
|
||||
|
||||
@Override public boolean connectionBecameIdle(
|
||||
ConnectionPool pool, RealConnection connection) {
|
||||
return pool.connectionBecameIdle(connection);
|
||||
}
|
||||
|
||||
@Override public RealConnection get(
|
||||
ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
|
||||
return pool.get(address, streamAllocation);
|
||||
}
|
||||
|
||||
@Override public void put(ConnectionPool pool, RealConnection connection) {
|
||||
pool.put(connection);
|
||||
}
|
||||
|
||||
@Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {
|
||||
return connectionPool.routeDatabase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket) {
|
||||
((RealCall) call).enqueue(responseCallback, forWebSocket);
|
||||
}
|
||||
|
||||
@Override public StreamAllocation callEngineGetStreamAllocation(Call call) {
|
||||
return ((RealCall) call).engine.streamAllocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean isFallback) {
|
||||
tlsConfiguration.apply(sslSocket, isFallback);
|
||||
}
|
||||
|
||||
@Override public HttpUrl getHttpUrlChecked(String url)
|
||||
throws MalformedURLException, UnknownHostException {
|
||||
return HttpUrl.getChecked(url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
final Dispatcher dispatcher;
|
||||
final Proxy proxy;
|
||||
final List<Protocol> protocols;
|
||||
final List<ConnectionSpec> connectionSpecs;
|
||||
final List<Interceptor> interceptors;
|
||||
final List<Interceptor> networkInterceptors;
|
||||
final ProxySelector proxySelector;
|
||||
final CookieJar cookieJar;
|
||||
final Cache cache;
|
||||
final InternalCache internalCache;
|
||||
final SocketFactory socketFactory;
|
||||
final SSLSocketFactory sslSocketFactory;
|
||||
final TrustRootIndex trustRootIndex;
|
||||
final HostnameVerifier hostnameVerifier;
|
||||
final CertificatePinner certificatePinner;
|
||||
final Authenticator proxyAuthenticator;
|
||||
final Authenticator authenticator;
|
||||
final ConnectionPool connectionPool;
|
||||
final Dns dns;
|
||||
final boolean followSslRedirects;
|
||||
final boolean followRedirects;
|
||||
final boolean retryOnConnectionFailure;
|
||||
final int connectTimeout;
|
||||
final int readTimeout;
|
||||
final int writeTimeout;
|
||||
|
||||
public OkHttpClient() {
|
||||
this(new Builder());
|
||||
}
|
||||
|
||||
private OkHttpClient(Builder builder) {
|
||||
this.dispatcher = builder.dispatcher;
|
||||
this.proxy = builder.proxy;
|
||||
this.protocols = builder.protocols;
|
||||
this.connectionSpecs = builder.connectionSpecs;
|
||||
this.interceptors = Util.immutableList(builder.interceptors);
|
||||
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
|
||||
this.proxySelector = builder.proxySelector;
|
||||
this.cookieJar = builder.cookieJar;
|
||||
this.cache = builder.cache;
|
||||
this.internalCache = builder.internalCache;
|
||||
this.socketFactory = builder.socketFactory;
|
||||
|
||||
boolean isTLS = false;
|
||||
for (ConnectionSpec spec : connectionSpecs) {
|
||||
isTLS = isTLS || spec.isTls();
|
||||
}
|
||||
|
||||
if (builder.sslSocketFactory != null || !isTLS) {
|
||||
this.sslSocketFactory = builder.sslSocketFactory;
|
||||
} else {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, null, null);
|
||||
this.sslSocketFactory = sslContext.getSocketFactory();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new AssertionError(); // The system has no TLS. Just give up.
|
||||
}
|
||||
}
|
||||
if (sslSocketFactory != null && builder.trustRootIndex == null) {
|
||||
X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
|
||||
if (trustManager == null) {
|
||||
throw new IllegalStateException("Unable to extract the trust manager on " + Platform.get()
|
||||
+ ", sslSocketFactory is " + sslSocketFactory.getClass());
|
||||
}
|
||||
this.trustRootIndex = Platform.get().trustRootIndex(trustManager);
|
||||
this.certificatePinner = builder.certificatePinner.newBuilder()
|
||||
.trustRootIndex(trustRootIndex)
|
||||
.build();
|
||||
} else {
|
||||
this.trustRootIndex = builder.trustRootIndex;
|
||||
this.certificatePinner = builder.certificatePinner;
|
||||
}
|
||||
this.hostnameVerifier = builder.hostnameVerifier;
|
||||
this.proxyAuthenticator = builder.proxyAuthenticator;
|
||||
this.authenticator = builder.authenticator;
|
||||
this.connectionPool = builder.connectionPool;
|
||||
this.dns = builder.dns;
|
||||
this.followSslRedirects = builder.followSslRedirects;
|
||||
this.followRedirects = builder.followRedirects;
|
||||
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
|
||||
this.connectTimeout = builder.connectTimeout;
|
||||
this.readTimeout = builder.readTimeout;
|
||||
this.writeTimeout = builder.writeTimeout;
|
||||
}
|
||||
|
||||
/** Default connect timeout (in milliseconds). */
|
||||
public int connectTimeoutMillis() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
/** Default read timeout (in milliseconds). */
|
||||
public int readTimeoutMillis() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
/** Default write timeout (in milliseconds). */
|
||||
public int writeTimeoutMillis() {
|
||||
return writeTimeout;
|
||||
}
|
||||
|
||||
public Proxy proxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public ProxySelector proxySelector() {
|
||||
return proxySelector;
|
||||
}
|
||||
|
||||
public CookieJar cookieJar() {
|
||||
return cookieJar;
|
||||
}
|
||||
|
||||
public Cache cache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
InternalCache internalCache() {
|
||||
return cache != null ? cache.internalCache : internalCache;
|
||||
}
|
||||
|
||||
public Dns dns() {
|
||||
return dns;
|
||||
}
|
||||
|
||||
public SocketFactory socketFactory() {
|
||||
return socketFactory;
|
||||
}
|
||||
|
||||
public SSLSocketFactory sslSocketFactory() {
|
||||
return sslSocketFactory;
|
||||
}
|
||||
|
||||
public HostnameVerifier hostnameVerifier() {
|
||||
return hostnameVerifier;
|
||||
}
|
||||
|
||||
public CertificatePinner certificatePinner() {
|
||||
return certificatePinner;
|
||||
}
|
||||
|
||||
public Authenticator authenticator() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
public Authenticator proxyAuthenticator() {
|
||||
return proxyAuthenticator;
|
||||
}
|
||||
|
||||
public ConnectionPool connectionPool() {
|
||||
return connectionPool;
|
||||
}
|
||||
|
||||
public boolean followSslRedirects() {
|
||||
return followSslRedirects;
|
||||
}
|
||||
|
||||
public boolean followRedirects() {
|
||||
return followRedirects;
|
||||
}
|
||||
|
||||
public boolean retryOnConnectionFailure() {
|
||||
return retryOnConnectionFailure;
|
||||
}
|
||||
|
||||
public Dispatcher dispatcher() {
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
public List<Protocol> protocols() {
|
||||
return protocols;
|
||||
}
|
||||
|
||||
public List<ConnectionSpec> connectionSpecs() {
|
||||
return connectionSpecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable list of interceptors that observe the full span of each call: from before
|
||||
* the connection is established (if any) until after the response source is selected (either the
|
||||
* origin server, cache, or both).
|
||||
*/
|
||||
public List<Interceptor> interceptors() {
|
||||
return interceptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable list of interceptors that observe a single network request and response.
|
||||
* These interceptors must call {@link Interceptor.Chain#proceed} exactly once: it is an error for
|
||||
* a network interceptor to short-circuit or repeat a network request.
|
||||
*/
|
||||
public List<Interceptor> networkInterceptors() {
|
||||
return networkInterceptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the {@code request} to be executed at some point in the future.
|
||||
*/
|
||||
@Override public Call newCall(Request request) {
|
||||
return new RealCall(this, request);
|
||||
}
|
||||
|
||||
public Builder newBuilder() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
Dispatcher dispatcher;
|
||||
Proxy proxy;
|
||||
List<Protocol> protocols;
|
||||
List<ConnectionSpec> connectionSpecs;
|
||||
final List<Interceptor> interceptors = new ArrayList<>();
|
||||
final List<Interceptor> networkInterceptors = new ArrayList<>();
|
||||
ProxySelector proxySelector;
|
||||
CookieJar cookieJar;
|
||||
Cache cache;
|
||||
InternalCache internalCache;
|
||||
SocketFactory socketFactory;
|
||||
SSLSocketFactory sslSocketFactory;
|
||||
TrustRootIndex trustRootIndex;
|
||||
HostnameVerifier hostnameVerifier;
|
||||
CertificatePinner certificatePinner;
|
||||
Authenticator proxyAuthenticator;
|
||||
Authenticator authenticator;
|
||||
ConnectionPool connectionPool;
|
||||
Dns dns;
|
||||
boolean followSslRedirects;
|
||||
boolean followRedirects;
|
||||
boolean retryOnConnectionFailure;
|
||||
int connectTimeout;
|
||||
int readTimeout;
|
||||
int writeTimeout;
|
||||
|
||||
public Builder() {
|
||||
dispatcher = new Dispatcher();
|
||||
protocols = DEFAULT_PROTOCOLS;
|
||||
connectionSpecs = DEFAULT_CONNECTION_SPECS;
|
||||
proxySelector = ProxySelector.getDefault();
|
||||
cookieJar = CookieJar.NO_COOKIES;
|
||||
socketFactory = SocketFactory.getDefault();
|
||||
hostnameVerifier = OkHostnameVerifier.INSTANCE;
|
||||
certificatePinner = CertificatePinner.DEFAULT;
|
||||
proxyAuthenticator = Authenticator.NONE;
|
||||
authenticator = Authenticator.NONE;
|
||||
connectionPool = new ConnectionPool();
|
||||
dns = Dns.SYSTEM;
|
||||
followSslRedirects = true;
|
||||
followRedirects = true;
|
||||
retryOnConnectionFailure = true;
|
||||
connectTimeout = 10_000;
|
||||
readTimeout = 10_000;
|
||||
writeTimeout = 10_000;
|
||||
}
|
||||
|
||||
Builder(OkHttpClient okHttpClient) {
|
||||
this.dispatcher = okHttpClient.dispatcher;
|
||||
this.proxy = okHttpClient.proxy;
|
||||
this.protocols = okHttpClient.protocols;
|
||||
this.connectionSpecs = okHttpClient.connectionSpecs;
|
||||
this.interceptors.addAll(okHttpClient.interceptors);
|
||||
this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
|
||||
this.proxySelector = okHttpClient.proxySelector;
|
||||
this.cookieJar = okHttpClient.cookieJar;
|
||||
this.internalCache = okHttpClient.internalCache;
|
||||
this.cache = okHttpClient.cache;
|
||||
this.socketFactory = okHttpClient.socketFactory;
|
||||
this.sslSocketFactory = okHttpClient.sslSocketFactory;
|
||||
this.trustRootIndex = okHttpClient.trustRootIndex;
|
||||
this.hostnameVerifier = okHttpClient.hostnameVerifier;
|
||||
this.certificatePinner = okHttpClient.certificatePinner;
|
||||
this.proxyAuthenticator = okHttpClient.proxyAuthenticator;
|
||||
this.authenticator = okHttpClient.authenticator;
|
||||
this.connectionPool = okHttpClient.connectionPool;
|
||||
this.dns = okHttpClient.dns;
|
||||
this.followSslRedirects = okHttpClient.followSslRedirects;
|
||||
this.followRedirects = okHttpClient.followRedirects;
|
||||
this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure;
|
||||
this.connectTimeout = okHttpClient.connectTimeout;
|
||||
this.readTimeout = okHttpClient.readTimeout;
|
||||
this.writeTimeout = okHttpClient.writeTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default connect timeout for new connections. A value of 0 means no timeout,
|
||||
* otherwise values must be between 1 and {@link Integer#MAX_VALUE} when converted to
|
||||
* milliseconds.
|
||||
*/
|
||||
public Builder connectTimeout(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.");
|
||||
if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
|
||||
connectTimeout = (int) millis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default read timeout for new connections. A value of 0 means no timeout, otherwise
|
||||
* values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
|
||||
*/
|
||||
public Builder readTimeout(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.");
|
||||
if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
|
||||
readTimeout = (int) millis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default write timeout for new connections. A value of 0 means no timeout, otherwise
|
||||
* values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
|
||||
*/
|
||||
public Builder writeTimeout(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.");
|
||||
if (millis == 0 && timeout > 0) throw new IllegalArgumentException("Timeout too small.");
|
||||
writeTimeout = (int) millis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HTTP proxy that will be used by connections created by this client. This takes
|
||||
* precedence over {@link #proxySelector}, which is only honored when this proxy is null (which
|
||||
* it is by default). To disable proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.
|
||||
*/
|
||||
public Builder proxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the proxy selection policy to be used if no {@link #proxy proxy} is specified
|
||||
* explicitly. The proxy selector may return multiple proxies; in that case they will be tried
|
||||
* in sequence until a successful connection is established.
|
||||
*
|
||||
* <p>If unset, the {@link ProxySelector#getDefault() system-wide default} proxy selector will
|
||||
* be used.
|
||||
*/
|
||||
public Builder proxySelector(ProxySelector proxySelector) {
|
||||
this.proxySelector = proxySelector;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the handler that can accept cookies from incoming HTTP responses and provides cookies to
|
||||
* outgoing HTTP requests.
|
||||
*
|
||||
* <p>If unset, {@linkplain CookieJar#NO_COOKIES no cookies} will be accepted nor provided.
|
||||
*/
|
||||
public Builder cookieJar(CookieJar cookieJar) {
|
||||
if (cookieJar == null) throw new NullPointerException("cookieJar == null");
|
||||
this.cookieJar = cookieJar;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the response cache to be used to read and write cached responses. */
|
||||
void setInternalCache(InternalCache internalCache) {
|
||||
this.internalCache = internalCache;
|
||||
this.cache = null;
|
||||
}
|
||||
|
||||
public Builder cache(Cache cache) {
|
||||
this.cache = cache;
|
||||
this.internalCache = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the DNS service used to lookup IP addresses for hostnames.
|
||||
*
|
||||
* <p>If unset, the {@link Dns#SYSTEM system-wide default} DNS will be used.
|
||||
*/
|
||||
public Builder dns(Dns dns) {
|
||||
if (dns == null) throw new NullPointerException("dns == null");
|
||||
this.dns = dns;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the socket factory used to create connections. OkHttp only uses the parameterless {@link
|
||||
* SocketFactory#createSocket() createSocket()} method to create unconnected sockets. Overriding
|
||||
* this method, e. g., allows the socket to be bound to a specific local address.
|
||||
*
|
||||
* <p>If unset, the {@link SocketFactory#getDefault() system-wide default} socket factory will
|
||||
* be used.
|
||||
*/
|
||||
public Builder socketFactory(SocketFactory socketFactory) {
|
||||
if (socketFactory == null) throw new NullPointerException("socketFactory == null");
|
||||
this.socketFactory = socketFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the socket factory used to secure HTTPS connections.
|
||||
*
|
||||
* <p>If unset, a lazily created SSL socket factory will be used.
|
||||
*/
|
||||
public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) {
|
||||
if (sslSocketFactory == null) throw new NullPointerException("sslSocketFactory == null");
|
||||
this.sslSocketFactory = sslSocketFactory;
|
||||
this.trustRootIndex = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the verifier used to confirm that response certificates apply to requested hostnames for
|
||||
* HTTPS connections.
|
||||
*
|
||||
* <p>If unset, a default hostname verifier will be used.
|
||||
*/
|
||||
public Builder hostnameVerifier(HostnameVerifier hostnameVerifier) {
|
||||
if (hostnameVerifier == null) throw new NullPointerException("hostnameVerifier == null");
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the certificate pinner that constrains which certificates are trusted. By default HTTPS
|
||||
* connections rely on only the {@link #sslSocketFactory SSL socket factory} to establish trust.
|
||||
* Pinning certificates avoids the need to trust certificate authorities.
|
||||
*/
|
||||
public Builder certificatePinner(CertificatePinner certificatePinner) {
|
||||
if (certificatePinner == null) throw new NullPointerException("certificatePinner == null");
|
||||
this.certificatePinner = certificatePinner;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authenticator used to respond to challenges from origin servers. Use {@link
|
||||
* #proxyAuthenticator} to set the authenticator for proxy servers.
|
||||
*
|
||||
* <p>If unset, the {@linkplain Authenticator#NONE no authentication will be attempted}.
|
||||
*/
|
||||
public Builder authenticator(Authenticator authenticator) {
|
||||
if (authenticator == null) throw new NullPointerException("authenticator == null");
|
||||
this.authenticator = authenticator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authenticator used to respond to challenges from proxy servers. Use {@link
|
||||
* #authenticator} to set the authenticator for origin servers.
|
||||
*
|
||||
* <p>If unset, the {@linkplain Authenticator#NONE no authentication will be attempted}.
|
||||
*/
|
||||
public Builder proxyAuthenticator(Authenticator proxyAuthenticator) {
|
||||
if (proxyAuthenticator == null) throw new NullPointerException("proxyAuthenticator == null");
|
||||
this.proxyAuthenticator = proxyAuthenticator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection pool used to recycle HTTP and HTTPS connections.
|
||||
*
|
||||
* <p>If unset, a new connection pool will be used.
|
||||
*/
|
||||
public Builder connectionPool(ConnectionPool connectionPool) {
|
||||
if (connectionPool == null) throw new NullPointerException("connectionPool == null");
|
||||
this.connectionPool = connectionPool;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure this client to follow redirects from HTTPS to HTTP and from HTTP to HTTPS.
|
||||
*
|
||||
* <p>If unset, protocol redirects will be followed. This is different than the built-in {@code
|
||||
* HttpURLConnection}'s default.
|
||||
*/
|
||||
public Builder followSslRedirects(boolean followProtocolRedirects) {
|
||||
this.followSslRedirects = followProtocolRedirects;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Configure this client to follow redirects. If unset, redirects be followed. */
|
||||
public Builder followRedirects(boolean followRedirects) {
|
||||
this.followRedirects = followRedirects;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure this client to retry or not when a connectivity problem is encountered. By default,
|
||||
* this client silently recovers from the following problems:
|
||||
*
|
||||
* <ul>
|
||||
* <li><strong>Unreachable IP addresses.</strong> If the URL's host has multiple IP addresses,
|
||||
* failure to reach any individual IP address doesn't fail the overall request. This can
|
||||
* increase availability of multi-homed services.
|
||||
* <li><strong>Stale pooled connections.</strong> The {@link ConnectionPool} reuses sockets
|
||||
* to decrease request latency, but these connections will occasionally time out.
|
||||
* <li><strong>Unreachable proxy servers.</strong> A {@link ProxySelector} can be used to
|
||||
* attempt multiple proxy servers in sequence, eventually falling back to a direct
|
||||
* connection.
|
||||
* </ul>
|
||||
*
|
||||
* Set this to false to avoid retrying requests when doing so is destructive. In this case the
|
||||
* calling application should do its own recovery of connectivity failures.
|
||||
*/
|
||||
public Builder retryOnConnectionFailure(boolean retryOnConnectionFailure) {
|
||||
this.retryOnConnectionFailure = retryOnConnectionFailure;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dispatcher used to set policy and execute asynchronous requests. Must not be null.
|
||||
*/
|
||||
public Builder dispatcher(Dispatcher dispatcher) {
|
||||
if (dispatcher == null) throw new IllegalArgumentException("dispatcher == null");
|
||||
this.dispatcher = dispatcher;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the protocols 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 protocols. 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 protocols 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-1">spdy/3.1</a>
|
||||
* <li><a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-17">h2</a>
|
||||
* </ul>
|
||||
*
|
||||
* <p><strong>This is an evolving set.</strong> Future releases include support for transitional
|
||||
* protocols. The http/1.1 transport will never be dropped.
|
||||
*
|
||||
* <p>If multiple protocols are specified, <a
|
||||
* href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg">ALPN</a> will be used to
|
||||
* negotiate a transport.
|
||||
*
|
||||
* <p>{@link Protocol#HTTP_1_0} is not supported in this set. Requests are initiated with {@code
|
||||
* HTTP/1.1} only. If the server responds with {@code HTTP/1.0}, that will be exposed by {@link
|
||||
* Response#protocol()}.
|
||||
*
|
||||
* @param protocols the protocols to use, in order of preference. The list must contain {@link
|
||||
* Protocol#HTTP_1_1}. It must not contain null or {@link Protocol#HTTP_1_0}.
|
||||
*/
|
||||
public Builder protocols(List<Protocol> protocols) {
|
||||
protocols = Util.immutableList(protocols);
|
||||
if (!protocols.contains(Protocol.HTTP_1_1)) {
|
||||
throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
|
||||
}
|
||||
if (protocols.contains(Protocol.HTTP_1_0)) {
|
||||
throw new IllegalArgumentException("protocols must not contain http/1.0: " + protocols);
|
||||
}
|
||||
if (protocols.contains(null)) {
|
||||
throw new IllegalArgumentException("protocols must not contain null");
|
||||
}
|
||||
this.protocols = Util.immutableList(protocols);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder connectionSpecs(List<ConnectionSpec> connectionSpecs) {
|
||||
this.connectionSpecs = Util.immutableList(connectionSpecs);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a modifiable list of interceptors that observe the full span of each call: from
|
||||
* before the connection is established (if any) until after the response source is selected
|
||||
* (either the origin server, cache, or both).
|
||||
*/
|
||||
public List<Interceptor> interceptors() {
|
||||
return interceptors;
|
||||
}
|
||||
|
||||
public Builder addInterceptor(Interceptor interceptor) {
|
||||
interceptors.add(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a modifiable list of interceptors that observe a single network request and response.
|
||||
* These interceptors must call {@link Interceptor.Chain#proceed} exactly once: it is an error
|
||||
* for a network interceptor to short-circuit or repeat a network request.
|
||||
*/
|
||||
public List<Interceptor> networkInterceptors() {
|
||||
return networkInterceptors;
|
||||
}
|
||||
|
||||
public Builder addNetworkInterceptor(Interceptor interceptor) {
|
||||
networkInterceptors.add(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OkHttpClient build() {
|
||||
return new OkHttpClient(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Protocols that OkHttp implements for <a
|
||||
* href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg">ALPN</a> selection.
|
||||
*
|
||||
* <h3>Protocol vs Scheme</h3> Despite its name, {@link java.net.URL#getProtocol()} returns the
|
||||
* {@linkplain java.net.URI#getScheme() scheme} (http, https, etc.) of the URL, not the protocol
|
||||
* (http/1.1, spdy/3.1, etc.). OkHttp uses the word <i>protocol</i> to identify how HTTP messages
|
||||
* are framed.
|
||||
*/
|
||||
public enum Protocol {
|
||||
/**
|
||||
* An obsolete plaintext framing that does not use persistent sockets by default.
|
||||
*/
|
||||
HTTP_1_0("http/1.0"),
|
||||
|
||||
/**
|
||||
* A plaintext framing that includes persistent connections.
|
||||
*
|
||||
* <p>This version of OkHttp implements <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC
|
||||
* 2616</a>, and tracks revisions to that spec.
|
||||
*/
|
||||
HTTP_1_1("http/1.1"),
|
||||
|
||||
/**
|
||||
* Chromium's binary-framed protocol that includes header compression, multiplexing multiple
|
||||
* requests on the same socket, and server-push. HTTP/1.1 semantics are layered on SPDY/3.
|
||||
*
|
||||
* <p>This version of OkHttp implements SPDY 3 <a
|
||||
* href="http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1">draft 3.1</a>. Future
|
||||
* releases of OkHttp may use this identifier for a newer draft of the SPDY spec.
|
||||
*/
|
||||
SPDY_3("spdy/3.1"),
|
||||
|
||||
/**
|
||||
* The IETF's binary-framed protocol that includes header compression, multiplexing multiple
|
||||
* requests on the same socket, and server-push. HTTP/1.1 semantics are layered on HTTP/2.
|
||||
*
|
||||
* <p>HTTP/2 requires deployments of HTTP/2 that use TLS 1.2 support {@linkplain
|
||||
* CipherSuite#TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256} , present in Java 8+ and Android 5+. Servers
|
||||
* that enforce this may send an exception message including the string {@code
|
||||
* INADEQUATE_SECURITY}.
|
||||
*/
|
||||
HTTP_2("h2");
|
||||
|
||||
private final String protocol;
|
||||
|
||||
Protocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the protocol identified by {@code protocol}.
|
||||
*
|
||||
* @throws IOException if {@code protocol} is unknown.
|
||||
*/
|
||||
public static Protocol get(String protocol) throws IOException {
|
||||
// Unroll the loop over values() to save an allocation.
|
||||
if (protocol.equals(HTTP_1_0.protocol)) return HTTP_1_0;
|
||||
if (protocol.equals(HTTP_1_1.protocol)) return HTTP_1_1;
|
||||
if (protocol.equals(HTTP_2.protocol)) return HTTP_2;
|
||||
if (protocol.equals(SPDY_3.protocol)) return SPDY_3;
|
||||
throw new IOException("Unexpected protocol: " + protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string used to identify this protocol for ALPN, like "http/1.1", "spdy/3.1" or
|
||||
* "h2".
|
||||
*/
|
||||
@Override public String toString() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ProtocolException;
|
||||
import java.util.logging.Level;
|
||||
import okhttp3.internal.NamedRunnable;
|
||||
import okhttp3.internal.http.HttpEngine;
|
||||
import okhttp3.internal.http.RequestException;
|
||||
import okhttp3.internal.http.RouteException;
|
||||
import okhttp3.internal.http.StreamAllocation;
|
||||
|
||||
import static okhttp3.internal.Internal.logger;
|
||||
import static okhttp3.internal.http.HttpEngine.MAX_FOLLOW_UPS;
|
||||
|
||||
final class RealCall implements Call {
|
||||
private final OkHttpClient client;
|
||||
|
||||
// Guarded by this.
|
||||
private boolean executed;
|
||||
volatile boolean canceled;
|
||||
|
||||
/** The application's original request unadulterated by redirects or auth headers. */
|
||||
Request originalRequest;
|
||||
HttpEngine engine;
|
||||
|
||||
protected RealCall(OkHttpClient client, Request originalRequest) {
|
||||
this.client = client;
|
||||
this.originalRequest = originalRequest;
|
||||
}
|
||||
|
||||
@Override public Request request() {
|
||||
return originalRequest;
|
||||
}
|
||||
|
||||
@Override public Response execute() throws IOException {
|
||||
synchronized (this) {
|
||||
if (executed) throw new IllegalStateException("Already Executed");
|
||||
executed = true;
|
||||
}
|
||||
try {
|
||||
client.dispatcher().executed(this);
|
||||
Response result = getResponseWithInterceptorChain(false);
|
||||
if (result == null) throw new IOException("Canceled");
|
||||
return result;
|
||||
} finally {
|
||||
client.dispatcher().finished(this);
|
||||
}
|
||||
}
|
||||
|
||||
Object tag() {
|
||||
return originalRequest.tag();
|
||||
}
|
||||
|
||||
@Override public void enqueue(Callback responseCallback) {
|
||||
enqueue(responseCallback, false);
|
||||
}
|
||||
|
||||
void enqueue(Callback responseCallback, boolean forWebSocket) {
|
||||
synchronized (this) {
|
||||
if (executed) throw new IllegalStateException("Already Executed");
|
||||
executed = true;
|
||||
}
|
||||
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
|
||||
}
|
||||
|
||||
@Override public void cancel() {
|
||||
canceled = true;
|
||||
if (engine != null) engine.cancel();
|
||||
}
|
||||
|
||||
@Override public synchronized boolean isExecuted() {
|
||||
return executed;
|
||||
}
|
||||
|
||||
@Override public boolean isCanceled() {
|
||||
return canceled;
|
||||
}
|
||||
|
||||
final class AsyncCall extends NamedRunnable {
|
||||
private final Callback responseCallback;
|
||||
private final boolean forWebSocket;
|
||||
|
||||
private AsyncCall(Callback responseCallback, boolean forWebSocket) {
|
||||
super("OkHttp %s", originalRequest.url().toString());
|
||||
this.responseCallback = responseCallback;
|
||||
this.forWebSocket = forWebSocket;
|
||||
}
|
||||
|
||||
String host() {
|
||||
return originalRequest.url().host();
|
||||
}
|
||||
|
||||
Request request() {
|
||||
return originalRequest;
|
||||
}
|
||||
|
||||
Object tag() {
|
||||
return originalRequest.tag();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
RealCall.this.cancel();
|
||||
}
|
||||
|
||||
RealCall get() {
|
||||
return RealCall.this;
|
||||
}
|
||||
|
||||
@Override protected void execute() {
|
||||
boolean signalledCallback = false;
|
||||
try {
|
||||
Response response = getResponseWithInterceptorChain(forWebSocket);
|
||||
if (canceled) {
|
||||
signalledCallback = true;
|
||||
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
|
||||
} else {
|
||||
signalledCallback = true;
|
||||
responseCallback.onResponse(RealCall.this, response);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (signalledCallback) {
|
||||
// Do not signal the callback twice!
|
||||
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
|
||||
} else {
|
||||
responseCallback.onFailure(RealCall.this, e);
|
||||
}
|
||||
} finally {
|
||||
client.dispatcher().finished(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string that describes this call. Doesn't include a full URL as that might contain
|
||||
* sensitive information.
|
||||
*/
|
||||
private String toLoggableString() {
|
||||
String string = canceled ? "canceled call" : "call";
|
||||
HttpUrl redactedUrl = originalRequest.url().resolve("/...");
|
||||
return string + " to " + redactedUrl;
|
||||
}
|
||||
|
||||
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
|
||||
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
|
||||
return chain.proceed(originalRequest);
|
||||
}
|
||||
|
||||
class ApplicationInterceptorChain implements Interceptor.Chain {
|
||||
private final int index;
|
||||
private final Request request;
|
||||
private final boolean forWebSocket;
|
||||
|
||||
ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
|
||||
this.index = index;
|
||||
this.request = request;
|
||||
this.forWebSocket = forWebSocket;
|
||||
}
|
||||
|
||||
@Override public Connection connection() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public Request request() {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override public Response proceed(Request request) throws IOException {
|
||||
// If there's another interceptor in the chain, call that.
|
||||
if (index < client.interceptors().size()) {
|
||||
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
|
||||
Interceptor interceptor = client.interceptors().get(index);
|
||||
Response interceptedResponse = interceptor.intercept(chain);
|
||||
|
||||
if (interceptedResponse == null) {
|
||||
throw new NullPointerException("application interceptor " + interceptor
|
||||
+ " returned null");
|
||||
}
|
||||
|
||||
return interceptedResponse;
|
||||
}
|
||||
|
||||
// No more interceptors. Do HTTP.
|
||||
return getResponse(request, forWebSocket);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the request and returns the response. May return null if this call was canceled.
|
||||
*/
|
||||
Response getResponse(Request request, boolean forWebSocket) throws IOException {
|
||||
// Copy body metadata to the appropriate request headers.
|
||||
RequestBody body = request.body();
|
||||
if (body != null) {
|
||||
Request.Builder requestBuilder = request.newBuilder();
|
||||
|
||||
MediaType contentType = body.contentType();
|
||||
if (contentType != null) {
|
||||
requestBuilder.header("Content-Type", contentType.toString());
|
||||
}
|
||||
|
||||
long contentLength = body.contentLength();
|
||||
if (contentLength != -1) {
|
||||
requestBuilder.header("Content-Length", Long.toString(contentLength));
|
||||
requestBuilder.removeHeader("Transfer-Encoding");
|
||||
} else {
|
||||
requestBuilder.header("Transfer-Encoding", "chunked");
|
||||
requestBuilder.removeHeader("Content-Length");
|
||||
}
|
||||
|
||||
request = requestBuilder.build();
|
||||
}
|
||||
|
||||
// Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
|
||||
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
|
||||
|
||||
int followUpCount = 0;
|
||||
while (true) {
|
||||
if (canceled) {
|
||||
engine.releaseStreamAllocation();
|
||||
throw new IOException("Canceled");
|
||||
}
|
||||
|
||||
boolean releaseConnection = true;
|
||||
try {
|
||||
engine.sendRequest();
|
||||
engine.readResponse();
|
||||
releaseConnection = false;
|
||||
} catch (RequestException e) {
|
||||
// The attempt to interpret the request failed. Give up.
|
||||
throw e.getCause();
|
||||
} catch (RouteException e) {
|
||||
// The attempt to connect via a route failed. The request will not have been sent.
|
||||
HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);
|
||||
if (retryEngine != null) {
|
||||
releaseConnection = false;
|
||||
engine = retryEngine;
|
||||
continue;
|
||||
}
|
||||
// Give up; recovery is not possible.
|
||||
throw e.getLastConnectException();
|
||||
} catch (IOException e) {
|
||||
// An attempt to communicate with a server failed. The request may have been sent.
|
||||
HttpEngine retryEngine = engine.recover(e, null);
|
||||
if (retryEngine != null) {
|
||||
releaseConnection = false;
|
||||
engine = retryEngine;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Give up; recovery is not possible.
|
||||
throw e;
|
||||
} finally {
|
||||
// We're throwing an unchecked exception. Release any resources.
|
||||
if (releaseConnection) {
|
||||
StreamAllocation streamAllocation = engine.close();
|
||||
streamAllocation.release();
|
||||
}
|
||||
}
|
||||
|
||||
Response response = engine.getResponse();
|
||||
Request followUp = engine.followUpRequest();
|
||||
|
||||
if (followUp == null) {
|
||||
if (!forWebSocket) {
|
||||
engine.releaseStreamAllocation();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
StreamAllocation streamAllocation = engine.close();
|
||||
|
||||
if (++followUpCount > MAX_FOLLOW_UPS) {
|
||||
streamAllocation.release();
|
||||
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
|
||||
}
|
||||
|
||||
if (!engine.sameConnection(followUp.url())) {
|
||||
streamAllocation.release();
|
||||
streamAllocation = null;
|
||||
}
|
||||
|
||||
request = followUp;
|
||||
engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
|
||||
response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,260 +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 okhttp3;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import okhttp3.internal.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
|
||||
* immutable.
|
||||
*/
|
||||
public final class Request {
|
||||
private final HttpUrl url;
|
||||
private final String method;
|
||||
private final Headers headers;
|
||||
private final RequestBody body;
|
||||
private final Object tag;
|
||||
|
||||
private volatile URI javaNetUri; // Lazily initialized.
|
||||
private volatile CacheControl cacheControl; // Lazily initialized.
|
||||
|
||||
private Request(Builder builder) {
|
||||
this.url = builder.url;
|
||||
this.method = builder.method;
|
||||
this.headers = builder.headers.build();
|
||||
this.body = builder.body;
|
||||
this.tag = builder.tag != null ? builder.tag : this;
|
||||
}
|
||||
|
||||
public HttpUrl url() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Headers headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public String header(String name) {
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
public List<String> headers(String name) {
|
||||
return headers.values(name);
|
||||
}
|
||||
|
||||
public RequestBody body() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public Object tag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public Builder newBuilder() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache control directives for this response. This is never null, even if this
|
||||
* response contains no {@code Cache-Control} header.
|
||||
*/
|
||||
public CacheControl cacheControl() {
|
||||
CacheControl result = cacheControl;
|
||||
return result != null ? result : (cacheControl = CacheControl.parse(headers));
|
||||
}
|
||||
|
||||
public boolean isHttps() {
|
||||
return url.isHttps();
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "Request{method="
|
||||
+ method
|
||||
+ ", url="
|
||||
+ url
|
||||
+ ", tag="
|
||||
+ (tag != this ? tag : null)
|
||||
+ '}';
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private HttpUrl url;
|
||||
private String method;
|
||||
private Headers.Builder headers;
|
||||
private RequestBody body;
|
||||
private Object tag;
|
||||
|
||||
public Builder() {
|
||||
this.method = "GET";
|
||||
this.headers = new Headers.Builder();
|
||||
}
|
||||
|
||||
private Builder(Request request) {
|
||||
this.url = request.url;
|
||||
this.method = request.method;
|
||||
this.body = request.body;
|
||||
this.tag = request.tag;
|
||||
this.headers = request.headers.newBuilder();
|
||||
}
|
||||
|
||||
public Builder url(HttpUrl url) {
|
||||
if (url == null) throw new IllegalArgumentException("url == null");
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL target of this request.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this
|
||||
* exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs.
|
||||
*/
|
||||
public Builder url(String url) {
|
||||
if (url == null) throw new IllegalArgumentException("url == null");
|
||||
|
||||
// Silently replace websocket URLs with HTTP URLs.
|
||||
if (url.regionMatches(true, 0, "ws:", 0, 3)) {
|
||||
url = "http:" + url.substring(3);
|
||||
} else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
|
||||
url = "https:" + url.substring(4);
|
||||
}
|
||||
|
||||
HttpUrl parsed = HttpUrl.parse(url);
|
||||
if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
|
||||
return url(parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL target of this request.
|
||||
*
|
||||
* @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code
|
||||
* https}.
|
||||
*/
|
||||
public Builder url(URL url) {
|
||||
if (url == null) throw new IllegalArgumentException("url == null");
|
||||
HttpUrl parsed = HttpUrl.get(url);
|
||||
if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
|
||||
return url(parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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".
|
||||
*
|
||||
* <p>Note that for some headers including {@code Content-Length} and {@code Content-Encoding},
|
||||
* OkHttp may replace {@code value} with a header derived from the request body.
|
||||
*/
|
||||
public Builder addHeader(String name, String value) {
|
||||
headers.add(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder removeHeader(String name) {
|
||||
headers.removeAll(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes all headers on this builder and adds {@code headers}. */
|
||||
public Builder headers(Headers headers) {
|
||||
this.headers = headers.newBuilder();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this request's {@code Cache-Control} header, replacing any cache control headers already
|
||||
* present. If {@code cacheControl} doesn't define any directives, this clears this request's
|
||||
* cache-control headers.
|
||||
*/
|
||||
public Builder cacheControl(CacheControl cacheControl) {
|
||||
String value = cacheControl.toString();
|
||||
if (value.isEmpty()) return removeHeader("Cache-Control");
|
||||
return header("Cache-Control", value);
|
||||
}
|
||||
|
||||
public Builder get() {
|
||||
return method("GET", null);
|
||||
}
|
||||
|
||||
public Builder head() {
|
||||
return method("HEAD", null);
|
||||
}
|
||||
|
||||
public Builder post(RequestBody body) {
|
||||
return method("POST", body);
|
||||
}
|
||||
|
||||
public Builder delete(RequestBody body) {
|
||||
return method("DELETE", body);
|
||||
}
|
||||
|
||||
public Builder delete() {
|
||||
return delete(RequestBody.create(null, new byte[0]));
|
||||
}
|
||||
|
||||
public Builder put(RequestBody body) {
|
||||
return method("PUT", body);
|
||||
}
|
||||
|
||||
public Builder patch(RequestBody body) {
|
||||
return method("PATCH", body);
|
||||
}
|
||||
|
||||
public Builder method(String method, RequestBody body) {
|
||||
if (method == null || method.length() == 0) {
|
||||
throw new IllegalArgumentException("method == null || method.length() == 0");
|
||||
}
|
||||
if (body != null && !HttpMethod.permitsRequestBody(method)) {
|
||||
throw new IllegalArgumentException("method " + method + " must not have a request body.");
|
||||
}
|
||||
if (body == null && HttpMethod.requiresRequestBody(method)) {
|
||||
throw new IllegalArgumentException("method " + method + " must have a request body.");
|
||||
}
|
||||
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() {
|
||||
if (url == null) throw new IllegalStateException("url == null");
|
||||
return new Request(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import okhttp3.internal.Util;
|
||||
import okio.BufferedSink;
|
||||
import okio.ByteString;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
public abstract class RequestBody {
|
||||
/** 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() throws IOException {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Writes the content of this request to {@code out}. */
|
||||
public abstract void writeTo(BufferedSink sink) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a new request body that transmits {@code content}. If {@code contentType} is non-null
|
||||
* and lacks a charset, this will use UTF-8.
|
||||
*/
|
||||
public static RequestBody create(MediaType contentType, String content) {
|
||||
Charset charset = Util.UTF_8;
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset();
|
||||
if (charset == null) {
|
||||
charset = Util.UTF_8;
|
||||
contentType = MediaType.parse(contentType + "; charset=utf-8");
|
||||
}
|
||||
}
|
||||
byte[] bytes = content.getBytes(charset);
|
||||
return create(contentType, bytes);
|
||||
}
|
||||
|
||||
/** Returns a new request body that transmits {@code content}. */
|
||||
public static RequestBody create(final MediaType contentType, final ByteString content) {
|
||||
return new RequestBody() {
|
||||
@Override public MediaType contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override public long contentLength() throws IOException {
|
||||
return content.size();
|
||||
}
|
||||
|
||||
@Override public void writeTo(BufferedSink sink) throws IOException {
|
||||
sink.write(content);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns a new request body that transmits {@code content}. */
|
||||
public static RequestBody create(final MediaType contentType, final byte[] content) {
|
||||
return create(contentType, content, 0, content.length);
|
||||
}
|
||||
|
||||
/** Returns a new request body that transmits {@code content}. */
|
||||
public static RequestBody create(final MediaType contentType, final byte[] content,
|
||||
final int offset, final int byteCount) {
|
||||
if (content == null) throw new NullPointerException("content == null");
|
||||
Util.checkOffsetAndCount(content.length, offset, byteCount);
|
||||
return new RequestBody() {
|
||||
@Override public MediaType contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return byteCount;
|
||||
}
|
||||
|
||||
@Override public void writeTo(BufferedSink sink) throws IOException {
|
||||
sink.write(content, offset, byteCount);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns a new request body that transmits the content of {@code file}. */
|
||||
public static RequestBody create(final MediaType contentType, final File file) {
|
||||
if (file == null) throw new NullPointerException("content == null");
|
||||
|
||||
return new RequestBody() {
|
||||
@Override public MediaType contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return file.length();
|
||||
}
|
||||
|
||||
@Override public void writeTo(BufferedSink sink) throws IOException {
|
||||
Source source = null;
|
||||
try {
|
||||
source = Okio.source(file);
|
||||
sink.writeAll(source);
|
||||
} finally {
|
||||
Util.closeQuietly(source);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,381 +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 okhttp3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import okhttp3.internal.http.OkHeaders;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
|
||||
import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
|
||||
import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
|
||||
import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
|
||||
import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
|
||||
import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
|
||||
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
|
||||
import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT;
|
||||
import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public final class Response {
|
||||
private final Request request;
|
||||
private final Protocol protocol;
|
||||
private final int code;
|
||||
private final String message;
|
||||
private final Handshake handshake;
|
||||
private final Headers headers;
|
||||
private final ResponseBody body;
|
||||
private Response networkResponse;
|
||||
private Response cacheResponse;
|
||||
private final Response priorResponse;
|
||||
|
||||
private volatile CacheControl cacheControl; // Lazily initialized.
|
||||
|
||||
private Response(Builder builder) {
|
||||
this.request = builder.request;
|
||||
this.protocol = builder.protocol;
|
||||
this.code = builder.code;
|
||||
this.message = builder.message;
|
||||
this.handshake = builder.handshake;
|
||||
this.headers = builder.headers.build();
|
||||
this.body = builder.body;
|
||||
this.networkResponse = builder.networkResponse;
|
||||
this.cacheResponse = builder.cacheResponse;
|
||||
this.priorResponse = builder.priorResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wire-level request that initiated this HTTP response. This is not
|
||||
* necessarily the same request issued by the application:
|
||||
*
|
||||
* <ul>
|
||||
* <li>It may be transformed by the HTTP client. For example, the client
|
||||
* may copy headers like {@code Content-Length} from the request body.
|
||||
* <li>It may be the request generated in response to an HTTP redirect or
|
||||
* authentication challenge. In this case the request URL may be
|
||||
* different than the initial request URL.
|
||||
* </ul>
|
||||
*/
|
||||
public Request request() {
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP protocol, such as {@link Protocol#HTTP_1_1} or {@link Protocol#HTTP_1_0}.
|
||||
*/
|
||||
public Protocol protocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/** Returns the HTTP status code. */
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the code is in [200..300), which means the request was successfully received,
|
||||
* understood, and accepted.
|
||||
*/
|
||||
public boolean isSuccessful() {
|
||||
return code >= 200 && code < 300;
|
||||
}
|
||||
|
||||
/** Returns the HTTP status message or null if it is unknown. */
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TLS handshake of the connection that carried this response, or null if the response
|
||||
* was received without TLS.
|
||||
*/
|
||||
public Handshake handshake() {
|
||||
return handshake;
|
||||
}
|
||||
|
||||
public List<String> headers(String name) {
|
||||
return headers.values(name);
|
||||
}
|
||||
|
||||
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 Headers headers() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peeks up to {@code byteCount} bytes from the response body and returns them as a new response
|
||||
* body. If fewer than {@code byteCount} bytes are in the response body, the full response body is
|
||||
* returned. If more than {@code byteCount} bytes are in the response body, the returned value
|
||||
* will be truncated to {@code byteCount} bytes.
|
||||
*
|
||||
* <p>It is an error to call this method after the body has been consumed.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this method loads the requested bytes into memory. Most
|
||||
* applications should set a modest limit on {@code byteCount}, such as 1 MiB.
|
||||
*/
|
||||
public ResponseBody peekBody(long byteCount) throws IOException {
|
||||
BufferedSource source = body.source();
|
||||
source.request(byteCount);
|
||||
Buffer copy = source.buffer().clone();
|
||||
|
||||
// There may be more than byteCount bytes in source.buffer(). If there is, return a prefix.
|
||||
Buffer result;
|
||||
if (copy.size() > byteCount) {
|
||||
result = new Buffer();
|
||||
result.write(copy, byteCount);
|
||||
copy.clear();
|
||||
} else {
|
||||
result = copy;
|
||||
}
|
||||
|
||||
return ResponseBody.create(body.contentType(), result.size(), result);
|
||||
}
|
||||
|
||||
public ResponseBody body() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public Builder newBuilder() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
/** Returns true if this response redirects to another resource. */
|
||||
public boolean isRedirect() {
|
||||
switch (code) {
|
||||
case HTTP_PERM_REDIRECT:
|
||||
case HTTP_TEMP_REDIRECT:
|
||||
case HTTP_MULT_CHOICE:
|
||||
case HTTP_MOVED_PERM:
|
||||
case HTTP_MOVED_TEMP:
|
||||
case HTTP_SEE_OTHER:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw response received from the network. Will be null if this response didn't use
|
||||
* the network, such as when the response is fully cached. The body of the returned response
|
||||
* should not be read.
|
||||
*/
|
||||
public Response networkResponse() {
|
||||
return networkResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw response received from the cache. Will be null if this response didn't use the
|
||||
* cache. For conditional get requests the cache response and network response may both be
|
||||
* non-null. The body of the returned response should not be read.
|
||||
*/
|
||||
public Response cacheResponse() {
|
||||
return cacheResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response for the HTTP redirect or authorization challenge that triggered this
|
||||
* response, or null if this response wasn't triggered by an automatic retry. The body of the
|
||||
* returned response should not be read because it has already been consumed by the redirecting
|
||||
* client.
|
||||
*/
|
||||
public Response priorResponse() {
|
||||
return priorResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authorization challenges appropriate for this response's code. If the response code
|
||||
* is 401 unauthorized, this returns the "WWW-Authenticate" challenges. If the response code is
|
||||
* 407 proxy unauthorized, this returns the "Proxy-Authenticate" challenges. Otherwise this
|
||||
* returns an empty list of challenges.
|
||||
*/
|
||||
public List<Challenge> challenges() {
|
||||
String responseField;
|
||||
if (code == HTTP_UNAUTHORIZED) {
|
||||
responseField = "WWW-Authenticate";
|
||||
} else if (code == HTTP_PROXY_AUTH) {
|
||||
responseField = "Proxy-Authenticate";
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return OkHeaders.parseChallenges(headers(), responseField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache control directives for this response. This is never null, even if this
|
||||
* response contains no {@code Cache-Control} header.
|
||||
*/
|
||||
public CacheControl cacheControl() {
|
||||
CacheControl result = cacheControl;
|
||||
return result != null ? result : (cacheControl = CacheControl.parse(headers));
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "Response{protocol="
|
||||
+ protocol
|
||||
+ ", code="
|
||||
+ code
|
||||
+ ", message="
|
||||
+ message
|
||||
+ ", url="
|
||||
+ request.url()
|
||||
+ '}';
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Request request;
|
||||
private Protocol protocol;
|
||||
private int code = -1;
|
||||
private String message;
|
||||
private Handshake handshake;
|
||||
private Headers.Builder headers;
|
||||
private ResponseBody body;
|
||||
private Response networkResponse;
|
||||
private Response cacheResponse;
|
||||
private Response priorResponse;
|
||||
|
||||
public Builder() {
|
||||
headers = new Headers.Builder();
|
||||
}
|
||||
|
||||
private Builder(Response response) {
|
||||
this.request = response.request;
|
||||
this.protocol = response.protocol;
|
||||
this.code = response.code;
|
||||
this.message = response.message;
|
||||
this.handshake = response.handshake;
|
||||
this.headers = response.headers.newBuilder();
|
||||
this.body = response.body;
|
||||
this.networkResponse = response.networkResponse;
|
||||
this.cacheResponse = response.cacheResponse;
|
||||
this.priorResponse = response.priorResponse;
|
||||
}
|
||||
|
||||
public Builder request(Request request) {
|
||||
this.request = request;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder protocol(Protocol protocol) {
|
||||
this.protocol = protocol;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder code(int code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder message(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder handshake(Handshake handshake) {
|
||||
this.handshake = handshake;
|
||||
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 "Set-Cookie".
|
||||
*/
|
||||
public Builder addHeader(String name, String value) {
|
||||
headers.add(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder removeHeader(String name) {
|
||||
headers.removeAll(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes all headers on this builder and adds {@code headers}. */
|
||||
public Builder headers(Headers headers) {
|
||||
this.headers = headers.newBuilder();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder body(ResponseBody body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder networkResponse(Response networkResponse) {
|
||||
if (networkResponse != null) checkSupportResponse("networkResponse", networkResponse);
|
||||
this.networkResponse = networkResponse;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cacheResponse(Response cacheResponse) {
|
||||
if (cacheResponse != null) checkSupportResponse("cacheResponse", cacheResponse);
|
||||
this.cacheResponse = cacheResponse;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void checkSupportResponse(String name, Response response) {
|
||||
if (response.body != null) {
|
||||
throw new IllegalArgumentException(name + ".body != null");
|
||||
} else if (response.networkResponse != null) {
|
||||
throw new IllegalArgumentException(name + ".networkResponse != null");
|
||||
} else if (response.cacheResponse != null) {
|
||||
throw new IllegalArgumentException(name + ".cacheResponse != null");
|
||||
} else if (response.priorResponse != null) {
|
||||
throw new IllegalArgumentException(name + ".priorResponse != null");
|
||||
}
|
||||
}
|
||||
|
||||
public Builder priorResponse(Response priorResponse) {
|
||||
if (priorResponse != null) checkPriorResponse(priorResponse);
|
||||
this.priorResponse = priorResponse;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void checkPriorResponse(Response response) {
|
||||
if (response.body != null) {
|
||||
throw new IllegalArgumentException("priorResponse.body != null");
|
||||
}
|
||||
}
|
||||
|
||||
public Response build() {
|
||||
if (request == null) throw new IllegalStateException("request == null");
|
||||
if (protocol == null) throw new IllegalStateException("protocol == null");
|
||||
if (code < 0) throw new IllegalStateException("code < 0: " + code);
|
||||
return new Response(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import okhttp3.internal.Util;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
|
||||
import static okhttp3.internal.Util.UTF_8;
|
||||
|
||||
/**
|
||||
* A one-shot stream from the origin server to the client application with the raw bytes of the
|
||||
* response body. Each response body is supported by an active connection to the webserver. This
|
||||
* imposes both obligations and limits on the client application.
|
||||
*
|
||||
* <h3>The response body must be closed.</h3>
|
||||
*
|
||||
* <p>Each response body is backed by a limited resource like a socket (live network responses) or
|
||||
* an open file (for cached responses). Failing to close the response body will leak these resources
|
||||
* and may ultimately cause the application to slow down or crash. Close the response body by
|
||||
* calling either {@link ResponseBody#close close()}, {@link InputStream#close()
|
||||
* byteStream().close()}, or {@link Reader#close() reader().close()}. The {@link #bytes()} and
|
||||
* {@link #string()} methods both close the response body automatically.
|
||||
*
|
||||
* <h3>The response body can be consumed only once.</h3>
|
||||
*
|
||||
* <p>This class may be used to stream very large responses. For example, it is possible to use this
|
||||
* class to read a response that is larger than the entire memory allocated to the current process.
|
||||
* It can even stream a response larger than the total storage on the current device, which is a
|
||||
* common requirement for video streaming applications.
|
||||
*
|
||||
* <p>Because this class does not buffer the full response in memory, the application may not
|
||||
* re-read the bytes of the response. Use this one shot to read the entire response into memory with
|
||||
* {@link #bytes()} or {@link #string()}. Or stream the response with either {@link #source()},
|
||||
* {@link #byteStream()}, or {@link #charStream()}.
|
||||
*/
|
||||
public abstract class ResponseBody implements Closeable {
|
||||
/** Multiple calls to {@link #charStream()} must return the same instance. */
|
||||
private Reader reader;
|
||||
|
||||
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 final InputStream byteStream() {
|
||||
return source().inputStream();
|
||||
}
|
||||
|
||||
public abstract BufferedSource source();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
BufferedSource source = source();
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = source.readByteArray();
|
||||
} finally {
|
||||
Util.closeQuietly(source);
|
||||
}
|
||||
if (contentLength != -1 && contentLength != bytes.length) {
|
||||
throw new IOException("Content-Length and stream length disagree");
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
Reader r = reader;
|
||||
return r != null ? r : (reader = new InputStreamReader(byteStream(), charset()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
Util.closeQuietly(source());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new response body that transmits {@code content}. If {@code contentType} is non-null
|
||||
* and lacks a charset, this will use UTF-8.
|
||||
*/
|
||||
public static ResponseBody create(MediaType contentType, String content) {
|
||||
Charset charset = UTF_8;
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset();
|
||||
if (charset == null) {
|
||||
charset = UTF_8;
|
||||
contentType = MediaType.parse(contentType + "; charset=utf-8");
|
||||
}
|
||||
}
|
||||
Buffer buffer = new Buffer().writeString(content, charset);
|
||||
return create(contentType, buffer.size(), buffer);
|
||||
}
|
||||
|
||||
/** Returns a new response body that transmits {@code content}. */
|
||||
public static ResponseBody create(final MediaType contentType, byte[] content) {
|
||||
Buffer buffer = new Buffer().write(content);
|
||||
return create(contentType, content.length, buffer);
|
||||
}
|
||||
|
||||
/** Returns a new response body that transmits {@code content}. */
|
||||
public static ResponseBody create(
|
||||
final MediaType contentType, final long contentLength, final BufferedSource content) {
|
||||
if (content == null) throw new NullPointerException("source == null");
|
||||
return new ResponseBody() {
|
||||
@Override public MediaType contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
@Override public BufferedSource source() {
|
||||
return content;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,99 +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 okhttp3;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* The concrete route used by a connection to reach an abstract origin server. When creating a
|
||||
* connection the client has many options:
|
||||
*
|
||||
* <ul>
|
||||
* <li><strong>HTTP proxy:</strong> a proxy server may be explicitly configured for the client.
|
||||
* Otherwise the {@linkplain java.net.ProxySelector proxy selector} is used. It may return
|
||||
* multiple proxies to attempt.
|
||||
* <li><strong>IP address:</strong> whether connecting directly to an origin server or a proxy,
|
||||
* opening a socket requires an IP address. The DNS server may return multiple IP addresses
|
||||
* to attempt.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Each route is a specific selection of these options.
|
||||
*/
|
||||
public final class Route {
|
||||
final Address address;
|
||||
final Proxy proxy;
|
||||
final InetSocketAddress inetSocketAddress;
|
||||
|
||||
public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress) {
|
||||
if (address == null) {
|
||||
throw new NullPointerException("address == null");
|
||||
}
|
||||
if (proxy == null) {
|
||||
throw new NullPointerException("proxy == null");
|
||||
}
|
||||
if (inetSocketAddress == null) {
|
||||
throw new NullPointerException("inetSocketAddress == null");
|
||||
}
|
||||
this.address = address;
|
||||
this.proxy = proxy;
|
||||
this.inetSocketAddress = inetSocketAddress;
|
||||
}
|
||||
|
||||
public Address address() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Proxy} of this route.
|
||||
*
|
||||
* <strong>Warning:</strong> This may disagree with {@link Address#proxy} when it is null. When
|
||||
* the address's proxy is null, the proxy selector is used.
|
||||
*/
|
||||
public Proxy proxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public InetSocketAddress socketAddress() {
|
||||
return inetSocketAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this route tunnels HTTPS through an HTTP proxy. See <a
|
||||
* href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section 5.2</a>.
|
||||
*/
|
||||
public boolean requiresTunnel() {
|
||||
return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object obj) {
|
||||
if (obj instanceof Route) {
|
||||
Route other = (Route) obj;
|
||||
return address.equals(other.address)
|
||||
&& proxy.equals(other.proxy)
|
||||
&& inetSocketAddress.equals(other.inetSocketAddress);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + address.hashCode();
|
||||
result = 31 * result + proxy.hashCode();
|
||||
result = 31 * result + inetSocketAddress.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3;
|
||||
|
||||
/**
|
||||
* Versions of TLS that can be offered when negotiating a secure socket. See {@link
|
||||
* javax.net.ssl.SSLSocket#setEnabledProtocols}.
|
||||
*/
|
||||
public enum TlsVersion {
|
||||
TLS_1_2("TLSv1.2"), // 2008.
|
||||
TLS_1_1("TLSv1.1"), // 2006.
|
||||
TLS_1_0("TLSv1"), // 1999.
|
||||
SSL_3_0("SSLv3"), // 1996.
|
||||
;
|
||||
|
||||
final String javaName;
|
||||
|
||||
TlsVersion(String javaName) {
|
||||
this.javaName = javaName;
|
||||
}
|
||||
|
||||
public static TlsVersion forJavaName(String javaName) {
|
||||
switch (javaName) {
|
||||
case "TLSv1.2":
|
||||
return TLS_1_2;
|
||||
case "TLSv1.1":
|
||||
return TLS_1_1;
|
||||
case "TLSv1":
|
||||
return TLS_1_0;
|
||||
case "SSLv3":
|
||||
return SSL_3_0;
|
||||
}
|
||||
throw new IllegalArgumentException("Unexpected TLS version: " + javaName);
|
||||
}
|
||||
|
||||
public String javaName() {
|
||||
return javaName;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.UnknownServiceException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLProtocolException;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import okhttp3.ConnectionSpec;
|
||||
|
||||
/**
|
||||
* Handles the connection spec fallback strategy: When a secure socket connection fails due to a
|
||||
* handshake / protocol problem the connection may be retried with different protocols. Instances
|
||||
* are stateful and should be created and used for a single connection attempt.
|
||||
*/
|
||||
public final class ConnectionSpecSelector {
|
||||
|
||||
private final List<ConnectionSpec> connectionSpecs;
|
||||
private int nextModeIndex;
|
||||
private boolean isFallbackPossible;
|
||||
private boolean isFallback;
|
||||
|
||||
public ConnectionSpecSelector(List<ConnectionSpec> connectionSpecs) {
|
||||
this.nextModeIndex = 0;
|
||||
this.connectionSpecs = connectionSpecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the supplied {@link SSLSocket} to connect to the specified host using an appropriate
|
||||
* {@link ConnectionSpec}. Returns the chosen {@link ConnectionSpec}, never {@code null}.
|
||||
*
|
||||
* @throws IOException if the socket does not support any of the TLS modes available
|
||||
*/
|
||||
public ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException {
|
||||
ConnectionSpec tlsConfiguration = null;
|
||||
for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) {
|
||||
ConnectionSpec connectionSpec = connectionSpecs.get(i);
|
||||
if (connectionSpec.isCompatible(sslSocket)) {
|
||||
tlsConfiguration = connectionSpec;
|
||||
nextModeIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tlsConfiguration == null) {
|
||||
// This may be the first time a connection has been attempted and the socket does not support
|
||||
// any the required protocols, or it may be a retry (but this socket supports fewer
|
||||
// protocols than was suggested by a prior socket).
|
||||
throw new UnknownServiceException(
|
||||
"Unable to find acceptable protocols. isFallback=" + isFallback
|
||||
+ ", modes=" + connectionSpecs
|
||||
+ ", supported protocols=" + Arrays.toString(sslSocket.getEnabledProtocols()));
|
||||
}
|
||||
|
||||
isFallbackPossible = isFallbackPossible(sslSocket);
|
||||
|
||||
Internal.instance.apply(tlsConfiguration, sslSocket, isFallback);
|
||||
|
||||
return tlsConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a failure to complete a connection. Determines the next {@link ConnectionSpec} to try,
|
||||
* if any.
|
||||
*
|
||||
* @return {@code true} if the connection should be retried using {@link
|
||||
* #configureSecureSocket(SSLSocket)} or {@code false} if not
|
||||
*/
|
||||
public boolean connectionFailed(IOException e) {
|
||||
// Any future attempt to connect using this strategy will be a fallback attempt.
|
||||
isFallback = true;
|
||||
|
||||
if (!isFallbackPossible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there was a protocol problem, don't recover.
|
||||
if (e instanceof ProtocolException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there was an interruption or timeout (SocketTimeoutException), don't recover.
|
||||
// For the socket connect timeout case we do not try the same host with a different
|
||||
// ConnectionSpec: we assume it is unreachable.
|
||||
if (e instanceof InterruptedIOException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for known client-side or negotiation errors that are unlikely to be fixed by trying
|
||||
// again with a different connection spec.
|
||||
if (e instanceof SSLHandshakeException) {
|
||||
// If the problem was a CertificateException from the X509TrustManager,
|
||||
// do not retry.
|
||||
if (e.getCause() instanceof CertificateException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (e instanceof SSLPeerUnverifiedException) {
|
||||
// e.g. a certificate pinning error.
|
||||
return false;
|
||||
}
|
||||
|
||||
// On Android, SSLProtocolExceptions can be caused by TLS_FALLBACK_SCSV failures, which means we
|
||||
// retry those when we probably should not.
|
||||
return (e instanceof SSLHandshakeException || e instanceof SSLProtocolException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if any later {@link ConnectionSpec} in the fallback strategy looks
|
||||
* possible based on the supplied {@link SSLSocket}. It assumes that a future socket will have the
|
||||
* same capabilities as the supplied socket.
|
||||
*/
|
||||
private boolean isFallbackPossible(SSLSocket socket) {
|
||||
for (int i = nextModeIndex; i < connectionSpecs.size(); i++) {
|
||||
if (connectionSpecs.get(i).isCompatible(socket)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import okio.Buffer;
|
||||
import okio.ForwardingSink;
|
||||
import okio.Sink;
|
||||
|
||||
/** A sink that never throws IOExceptions, even if the underlying sink does. */
|
||||
class FaultHidingSink extends ForwardingSink {
|
||||
private boolean hasErrors;
|
||||
|
||||
public FaultHidingSink(Sink delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override public void write(Buffer source, long byteCount) throws IOException {
|
||||
if (hasErrors) {
|
||||
source.skip(byteCount);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
super.write(source, byteCount);
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
onException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void flush() throws IOException {
|
||||
if (hasErrors) return;
|
||||
try {
|
||||
super.flush();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
onException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
if (hasErrors) return;
|
||||
try {
|
||||
super.close();
|
||||
} catch (IOException e) {
|
||||
hasErrors = true;
|
||||
onException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onException(IOException e) {
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3.internal;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.logging.Logger;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import okhttp3.Address;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.ConnectionSpec;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.internal.http.StreamAllocation;
|
||||
import okhttp3.internal.io.RealConnection;
|
||||
|
||||
/**
|
||||
* Escalate internal APIs in {@code okhttp3} so they can be used from OkHttp's implementation
|
||||
* packages. The only implementation of this interface is in {@link OkHttpClient}.
|
||||
*/
|
||||
public abstract class Internal {
|
||||
public static final Logger logger = Logger.getLogger(OkHttpClient.class.getName());
|
||||
|
||||
public static void initializeInstanceForTests() {
|
||||
// Needed in tests to ensure that the instance is actually pointing to something.
|
||||
new OkHttpClient();
|
||||
}
|
||||
|
||||
public static Internal instance;
|
||||
|
||||
public abstract void addLenient(Headers.Builder builder, String line);
|
||||
|
||||
public abstract void addLenient(Headers.Builder builder, String name, String value);
|
||||
|
||||
public abstract void setCache(OkHttpClient.Builder builder, InternalCache internalCache);
|
||||
|
||||
public abstract InternalCache internalCache(OkHttpClient client);
|
||||
|
||||
public abstract RealConnection get(
|
||||
ConnectionPool pool, Address address, StreamAllocation streamAllocation);
|
||||
|
||||
public abstract void put(ConnectionPool pool, RealConnection connection);
|
||||
|
||||
public abstract boolean connectionBecameIdle(ConnectionPool pool, RealConnection connection);
|
||||
|
||||
public abstract RouteDatabase routeDatabase(ConnectionPool connectionPool);
|
||||
|
||||
public abstract void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket,
|
||||
boolean isFallback);
|
||||
|
||||
public abstract HttpUrl getHttpUrlChecked(String url)
|
||||
throws MalformedURLException, UnknownHostException;
|
||||
|
||||
// TODO delete the following when web sockets move into the main package.
|
||||
public abstract void callEnqueue(Call call, Callback responseCallback, boolean forWebSocket);
|
||||
|
||||
public abstract StreamAllocation callEngineGetStreamAllocation(Call call);
|
||||
}
|
||||
@@ -1,51 +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 okhttp3.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.internal.http.CacheRequest;
|
||||
import okhttp3.internal.http.CacheStrategy;
|
||||
|
||||
/**
|
||||
* OkHttp's internal cache interface. Applications shouldn't implement this: instead use {@link
|
||||
* okhttp3.Cache}.
|
||||
*/
|
||||
public interface InternalCache {
|
||||
Response get(Request request) throws IOException;
|
||||
|
||||
CacheRequest put(Response response) throws IOException;
|
||||
|
||||
/**
|
||||
* Remove any cache entries for the supplied {@code request}. This is invoked when the client
|
||||
* invalidates the cache, such as when making POST requests.
|
||||
*/
|
||||
void remove(Request request) throws IOException;
|
||||
|
||||
/**
|
||||
* Handles a conditional request hit by updating the stored cache response with the headers from
|
||||
* {@code network}. The cached response body is not updated. If the stored response has changed
|
||||
* since {@code cached} was returned, this does nothing.
|
||||
*/
|
||||
void update(Response cached, Response network) throws IOException;
|
||||
|
||||
/** Track an conditional GET that was satisfied by this cache. */
|
||||
void trackConditionalCacheHit();
|
||||
|
||||
/** Track an HTTP response being satisfied with {@code cacheStrategy}. */
|
||||
void trackResponse(CacheStrategy cacheStrategy);
|
||||
}
|
||||
@@ -1,39 +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 okhttp3.internal;
|
||||
|
||||
/**
|
||||
* Runnable implementation which always sets its thread name.
|
||||
*/
|
||||
public abstract class NamedRunnable implements Runnable {
|
||||
protected final String name;
|
||||
|
||||
public NamedRunnable(String format, Object... args) {
|
||||
this.name = String.format(format, args);
|
||||
}
|
||||
|
||||
@Override public final void run() {
|
||||
String oldName = Thread.currentThread().getName();
|
||||
Thread.currentThread().setName(name);
|
||||
try {
|
||||
execute();
|
||||
} finally {
|
||||
Thread.currentThread().setName(oldName);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void execute();
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* 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 okhttp3.internal;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Duck-typing for methods: Represents a method that may or may not be present on an object.
|
||||
*
|
||||
* @param <T> the type of the object the method might be on, typically an interface or base class
|
||||
*/
|
||||
class OptionalMethod<T> {
|
||||
|
||||
/** The return type of the method. null means "don't care". */
|
||||
private final Class<?> returnType;
|
||||
|
||||
private final String methodName;
|
||||
|
||||
private final Class[] methodParams;
|
||||
|
||||
/**
|
||||
* Creates an optional method.
|
||||
*
|
||||
* @param returnType the return type to required, null if it does not matter
|
||||
* @param methodName the name of the method
|
||||
* @param methodParams the method parameter types
|
||||
*/
|
||||
public OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) {
|
||||
this.returnType = returnType;
|
||||
this.methodName = methodName;
|
||||
this.methodParams = methodParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the method exists on the supplied {@code target}.
|
||||
*/
|
||||
public boolean isSupported(T target) {
|
||||
return getMethod(target.getClass()) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method on {@code target} with {@code args}. If the method does not exist or is not
|
||||
* public then {@code null} is returned. See also {@link #invokeOptionalWithoutCheckedException}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the arguments are invalid
|
||||
* @throws InvocationTargetException if the invocation throws an exception
|
||||
*/
|
||||
public Object invokeOptional(T target, Object... args) throws InvocationTargetException {
|
||||
Method m = getMethod(target.getClass());
|
||||
if (m == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return m.invoke(target, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method on {@code target}. If the method does not exist or is not public then
|
||||
* {@code null} is returned. Any RuntimeException thrown by the method is thrown, checked
|
||||
* exceptions are wrapped in an {@link AssertionError}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the arguments are invalid
|
||||
*/
|
||||
public Object invokeOptionalWithoutCheckedException(T target, Object... args) {
|
||||
try {
|
||||
return invokeOptional(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable targetException = e.getTargetException();
|
||||
if (targetException instanceof RuntimeException) {
|
||||
throw (RuntimeException) targetException;
|
||||
}
|
||||
AssertionError error = new AssertionError("Unexpected exception");
|
||||
error.initCause(targetException);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method on {@code target} with {@code args}. Throws an error if the method is not
|
||||
* supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the arguments are invalid
|
||||
* @throws InvocationTargetException if the invocation throws an exception
|
||||
*/
|
||||
public Object invoke(T target, Object... args) throws InvocationTargetException {
|
||||
Method m = getMethod(target.getClass());
|
||||
if (m == null) {
|
||||
throw new AssertionError("Method " + methodName + " not supported for object " + target);
|
||||
}
|
||||
try {
|
||||
return m.invoke(target, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Method should be public: we checked.
|
||||
AssertionError error = new AssertionError("Unexpectedly could not call: " + m);
|
||||
error.initCause(e);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method on {@code target}. Throws an error if the method is not supported. Any
|
||||
* RuntimeException thrown by the method is thrown, checked exceptions are wrapped in an {@link
|
||||
* AssertionError}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the arguments are invalid
|
||||
*/
|
||||
public Object invokeWithoutCheckedException(T target, Object... args) {
|
||||
try {
|
||||
return invoke(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable targetException = e.getTargetException();
|
||||
if (targetException instanceof RuntimeException) {
|
||||
throw (RuntimeException) targetException;
|
||||
}
|
||||
AssertionError error = new AssertionError("Unexpected exception");
|
||||
error.initCause(targetException);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a lookup for the method. No caching. In order to return a method the method name and
|
||||
* arguments must match those specified when the {@link OptionalMethod} was created. If the return
|
||||
* type is specified (i.e. non-null) it must also be compatible. The method must also be public.
|
||||
*/
|
||||
private Method getMethod(Class<?> clazz) {
|
||||
Method method = null;
|
||||
if (methodName != null) {
|
||||
method = getPublicMethod(clazz, methodName, methodParams);
|
||||
if (method != null
|
||||
&& returnType != null
|
||||
&& !returnType.isAssignableFrom(method.getReturnType())) {
|
||||
|
||||
// If the return type is non-null it must be compatible.
|
||||
method = null;
|
||||
}
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private static Method getPublicMethod(Class<?> clazz, String methodName, Class[] parameterTypes) {
|
||||
Method method = null;
|
||||
try {
|
||||
method = clazz.getMethod(methodName, parameterTypes);
|
||||
if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
|
||||
method = null;
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
// None.
|
||||
}
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,435 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Square, Inc.
|
||||
* Copyright (C) 2012 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 okhttp3.internal;
|
||||
|
||||
import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import okhttp3.Protocol;
|
||||
import okhttp3.internal.tls.AndroidTrustRootIndex;
|
||||
import okhttp3.internal.tls.RealTrustRootIndex;
|
||||
import okhttp3.internal.tls.TrustRootIndex;
|
||||
import okio.Buffer;
|
||||
|
||||
import static okhttp3.internal.Internal.logger;
|
||||
|
||||
/**
|
||||
* Access to platform-specific features.
|
||||
*
|
||||
* <h3>Server name indication (SNI)</h3>
|
||||
*
|
||||
* <p>Supported on Android 2.3+.
|
||||
*
|
||||
* <h3>Session Tickets</h3>
|
||||
*
|
||||
* <p>Supported on Android 2.3+.
|
||||
*
|
||||
* <h3>Android Traffic Stats (Socket Tagging)</h3>
|
||||
*
|
||||
* <p>Supported on Android 4.0+.
|
||||
*
|
||||
* <h3>ALPN (Application Layer Protocol Negotiation)</h3>
|
||||
*
|
||||
* <p>Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was
|
||||
* unstable.
|
||||
*
|
||||
* Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library).
|
||||
*
|
||||
* <h3>Trust Manager Extraction</h3>
|
||||
*
|
||||
* <p>Supported on Android 2.3+ and OpenJDK 7+. There are no public APIs to recover the trust
|
||||
* manager that was used to create an {@link SSLSocketFactory}.
|
||||
*/
|
||||
public class Platform {
|
||||
private static final Platform PLATFORM = findPlatform();
|
||||
|
||||
public static Platform get() {
|
||||
return PLATFORM;
|
||||
}
|
||||
|
||||
/** Prefix used on custom headers. */
|
||||
public String getPrefix() {
|
||||
return "OkHttp";
|
||||
}
|
||||
|
||||
public void logW(String warning) {
|
||||
System.out.println(warning);
|
||||
}
|
||||
|
||||
public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
|
||||
// Attempt to get the trust manager from an OpenJDK socket factory. We attempt this on all
|
||||
// platforms in order to support Robolectric, which mixes classes from both Android and the
|
||||
// Oracle JDK. Note that we don't support HTTP/2 or other nice features on Robolectric.
|
||||
try {
|
||||
Class<?> sslContextClass = Class.forName("sun.security.ssl.SSLContextImpl");
|
||||
Object context = readFieldOrNull(sslSocketFactory, sslContextClass, "context");
|
||||
if (context == null) return null;
|
||||
return readFieldOrNull(context, X509TrustManager.class, "trustManager");
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public TrustRootIndex trustRootIndex(X509TrustManager trustManager) {
|
||||
return new RealTrustRootIndex(trustManager.getAcceptedIssuers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure TLS extensions on {@code sslSocket} for {@code route}.
|
||||
*
|
||||
* @param hostname non-null for client-side handshakes; null for server-side handshakes.
|
||||
*/
|
||||
public void configureTlsExtensions(SSLSocket sslSocket, String hostname,
|
||||
List<Protocol> protocols) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the TLS handshake to release resources allocated by {@link
|
||||
* #configureTlsExtensions}.
|
||||
*/
|
||||
public void afterHandshake(SSLSocket sslSocket) {
|
||||
}
|
||||
|
||||
/** Returns the negotiated protocol, or null if no protocol was negotiated. */
|
||||
public String getSelectedProtocol(SSLSocket socket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void connectSocket(Socket socket, InetSocketAddress address,
|
||||
int connectTimeout) throws IOException {
|
||||
socket.connect(address, connectTimeout);
|
||||
}
|
||||
|
||||
public void log(String message) {
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
/** Attempt to match the host runtime to a capable Platform implementation. */
|
||||
private static Platform findPlatform() {
|
||||
// Attempt to find Android 2.3+ APIs.
|
||||
try {
|
||||
Class<?> sslParametersClass;
|
||||
try {
|
||||
sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl");
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Older platform before being unbundled.
|
||||
sslParametersClass = Class.forName(
|
||||
"org.apache.harmony.xnet.provider.jsse.SSLParametersImpl");
|
||||
}
|
||||
|
||||
OptionalMethod<Socket> setUseSessionTickets = new OptionalMethod<>(
|
||||
null, "setUseSessionTickets", boolean.class);
|
||||
OptionalMethod<Socket> setHostname = new OptionalMethod<>(
|
||||
null, "setHostname", String.class);
|
||||
OptionalMethod<Socket> getAlpnSelectedProtocol = null;
|
||||
OptionalMethod<Socket> setAlpnProtocols = null;
|
||||
|
||||
// Attempt to find Android 5.0+ APIs.
|
||||
try {
|
||||
Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.
|
||||
getAlpnSelectedProtocol = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
|
||||
setAlpnProtocols = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
return new Android(sslParametersClass, setUseSessionTickets, setHostname,
|
||||
getAlpnSelectedProtocol, setAlpnProtocols);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// This isn't an Android runtime.
|
||||
}
|
||||
|
||||
// Find Jetty's ALPN extension for OpenJDK.
|
||||
try {
|
||||
String negoClassName = "org.eclipse.jetty.alpn.ALPN";
|
||||
Class<?> negoClass = Class.forName(negoClassName);
|
||||
Class<?> providerClass = Class.forName(negoClassName + "$Provider");
|
||||
Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
|
||||
Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
|
||||
Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
|
||||
Method getMethod = negoClass.getMethod("get", SSLSocket.class);
|
||||
Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);
|
||||
return new JdkWithJettyBootPlatform(
|
||||
putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
// Probably an Oracle JDK like OpenJDK.
|
||||
return new Platform();
|
||||
}
|
||||
|
||||
/** Android 2.3 or better. */
|
||||
private static class Android extends Platform {
|
||||
private static final int MAX_LOG_LENGTH = 4000;
|
||||
|
||||
private final Class<?> sslParametersClass;
|
||||
private final OptionalMethod<Socket> setUseSessionTickets;
|
||||
private final OptionalMethod<Socket> setHostname;
|
||||
|
||||
// Non-null on Android 5.0+.
|
||||
private final OptionalMethod<Socket> getAlpnSelectedProtocol;
|
||||
private final OptionalMethod<Socket> setAlpnProtocols;
|
||||
|
||||
public Android(Class<?> sslParametersClass, OptionalMethod<Socket> setUseSessionTickets,
|
||||
OptionalMethod<Socket> setHostname, OptionalMethod<Socket> getAlpnSelectedProtocol,
|
||||
OptionalMethod<Socket> setAlpnProtocols) {
|
||||
this.sslParametersClass = sslParametersClass;
|
||||
this.setUseSessionTickets = setUseSessionTickets;
|
||||
this.setHostname = setHostname;
|
||||
this.getAlpnSelectedProtocol = getAlpnSelectedProtocol;
|
||||
this.setAlpnProtocols = setAlpnProtocols;
|
||||
}
|
||||
|
||||
@Override public void connectSocket(Socket socket, InetSocketAddress address,
|
||||
int connectTimeout) throws IOException {
|
||||
try {
|
||||
socket.connect(address, connectTimeout);
|
||||
} catch (AssertionError e) {
|
||||
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
|
||||
throw e;
|
||||
} catch (SecurityException e) {
|
||||
// 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(e);
|
||||
throw ioException;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
|
||||
Object context = readFieldOrNull(sslSocketFactory, sslParametersClass, "sslParameters");
|
||||
if (context == null) {
|
||||
// If that didn't work, try the Google Play Services SSL provider before giving up. This
|
||||
// must be loaded by the SSLSocketFactory's class loader.
|
||||
try {
|
||||
Class<?> gmsSslParametersClass = Class.forName(
|
||||
"com.google.android.gms.org.conscrypt.SSLParametersImpl", false,
|
||||
sslSocketFactory.getClass().getClassLoader());
|
||||
context = readFieldOrNull(sslSocketFactory, gmsSslParametersClass, "sslParameters");
|
||||
} catch (ClassNotFoundException e) {
|
||||
return super.trustManager(sslSocketFactory);
|
||||
}
|
||||
}
|
||||
|
||||
X509TrustManager x509TrustManager = readFieldOrNull(
|
||||
context, X509TrustManager.class, "x509TrustManager");
|
||||
if (x509TrustManager != null) return x509TrustManager;
|
||||
|
||||
return readFieldOrNull(context, X509TrustManager.class, "trustManager");
|
||||
}
|
||||
|
||||
@Override public TrustRootIndex trustRootIndex(X509TrustManager trustManager) {
|
||||
TrustRootIndex result = AndroidTrustRootIndex.get(trustManager);
|
||||
if (result != null) return result;
|
||||
return super.trustRootIndex(trustManager);
|
||||
}
|
||||
|
||||
@Override public void configureTlsExtensions(
|
||||
SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
|
||||
// Enable SNI and session tickets.
|
||||
if (hostname != null) {
|
||||
setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true);
|
||||
setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname);
|
||||
}
|
||||
|
||||
// Enable ALPN.
|
||||
if (setAlpnProtocols != null && setAlpnProtocols.isSupported(sslSocket)) {
|
||||
Object[] parameters = {concatLengthPrefixed(protocols)};
|
||||
setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public String getSelectedProtocol(SSLSocket socket) {
|
||||
if (getAlpnSelectedProtocol == null) return null;
|
||||
if (!getAlpnSelectedProtocol.isSupported(socket)) return null;
|
||||
|
||||
byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);
|
||||
return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;
|
||||
}
|
||||
|
||||
@Override public void log(String message) {
|
||||
// Split by line, then ensure each line can fit into Log's maximum length.
|
||||
for (int i = 0, length = message.length(); i < length; i++) {
|
||||
int newline = message.indexOf('\n', i);
|
||||
newline = newline != -1 ? newline : length;
|
||||
do {
|
||||
int end = Math.min(newline, i + MAX_LOG_LENGTH);
|
||||
Log.d("OkHttp", message.substring(i, end));
|
||||
i = end;
|
||||
} while (i < newline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path.
|
||||
*/
|
||||
private static class JdkWithJettyBootPlatform extends Platform {
|
||||
private final Method putMethod;
|
||||
private final Method getMethod;
|
||||
private final Method removeMethod;
|
||||
private final Class<?> clientProviderClass;
|
||||
private final Class<?> serverProviderClass;
|
||||
|
||||
public JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod,
|
||||
Class<?> clientProviderClass, Class<?> serverProviderClass) {
|
||||
this.putMethod = putMethod;
|
||||
this.getMethod = getMethod;
|
||||
this.removeMethod = removeMethod;
|
||||
this.clientProviderClass = clientProviderClass;
|
||||
this.serverProviderClass = serverProviderClass;
|
||||
}
|
||||
|
||||
@Override public void configureTlsExtensions(
|
||||
SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
|
||||
List<String> names = new ArrayList<>(protocols.size());
|
||||
for (int i = 0, size = protocols.size(); i < size; i++) {
|
||||
Protocol protocol = protocols.get(i);
|
||||
if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
|
||||
names.add(protocol.toString());
|
||||
}
|
||||
try {
|
||||
Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
|
||||
new Class[] {clientProviderClass, serverProviderClass}, new JettyNegoProvider(names));
|
||||
putMethod.invoke(null, sslSocket, provider);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void afterHandshake(SSLSocket sslSocket) {
|
||||
try {
|
||||
removeMethod.invoke(null, sslSocket);
|
||||
} catch (IllegalAccessException | InvocationTargetException ignored) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public String getSelectedProtocol(SSLSocket socket) {
|
||||
try {
|
||||
JettyNegoProvider provider =
|
||||
(JettyNegoProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
|
||||
if (!provider.unsupported && provider.selected == null) {
|
||||
logger.log(Level.INFO, "ALPN callback dropped: SPDY and HTTP/2 are disabled. "
|
||||
+ "Is alpn-boot on the boot class path?");
|
||||
return null;
|
||||
}
|
||||
return provider.unsupported ? null : provider.selected;
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the methods of ALPN's ClientProvider and ServerProvider without a compile-time
|
||||
* dependency on those interfaces.
|
||||
*/
|
||||
private static class JettyNegoProvider implements InvocationHandler {
|
||||
/** This peer's supported protocols. */
|
||||
private final List<String> protocols;
|
||||
/** Set when remote peer notifies ALPN is unsupported. */
|
||||
private boolean unsupported;
|
||||
/** The protocol the server selected. */
|
||||
private String selected;
|
||||
|
||||
public JettyNegoProvider(List<String> protocols) {
|
||||
this.protocols = protocols;
|
||||
}
|
||||
|
||||
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
String methodName = method.getName();
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (args == null) {
|
||||
args = Util.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
if (methodName.equals("supports") && boolean.class == returnType) {
|
||||
return true; // ALPN is supported.
|
||||
} else if (methodName.equals("unsupported") && void.class == returnType) {
|
||||
this.unsupported = true; // Peer doesn't support ALPN.
|
||||
return null;
|
||||
} else if (methodName.equals("protocols") && args.length == 0) {
|
||||
return protocols; // Client advertises these protocols.
|
||||
} else if ((methodName.equals("selectProtocol") || methodName.equals("select"))
|
||||
&& String.class == returnType && args.length == 1 && args[0] instanceof List) {
|
||||
List<String> peerProtocols = (List) args[0];
|
||||
// Pick the first known protocol the peer advertises.
|
||||
for (int i = 0, size = peerProtocols.size(); i < size; i++) {
|
||||
if (protocols.contains(peerProtocols.get(i))) {
|
||||
return selected = peerProtocols.get(i);
|
||||
}
|
||||
}
|
||||
return selected = protocols.get(0); // On no intersection, try peer's first protocol.
|
||||
} else if ((methodName.equals("protocolSelected") || methodName.equals("selected"))
|
||||
&& args.length == 1) {
|
||||
this.selected = (String) args[0]; // Server selected this protocol.
|
||||
return null;
|
||||
} else {
|
||||
return method.invoke(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the concatenation of 8-bit, length prefixed protocol names.
|
||||
* http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
|
||||
*/
|
||||
static byte[] concatLengthPrefixed(List<Protocol> protocols) {
|
||||
Buffer result = new Buffer();
|
||||
for (int i = 0, size = protocols.size(); i < size; i++) {
|
||||
Protocol protocol = protocols.get(i);
|
||||
if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
|
||||
result.writeByte(protocol.toString().length());
|
||||
result.writeUtf8(protocol.toString());
|
||||
}
|
||||
return result.readByteArray();
|
||||
}
|
||||
|
||||
static <T> T readFieldOrNull(Object instance, Class<T> fieldType, String fieldName) {
|
||||
for (Class<?> c = instance.getClass(); c != Object.class; c = c.getSuperclass()) {
|
||||
try {
|
||||
Field field = c.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(instance);
|
||||
if (value == null || !fieldType.isInstance(value)) return null;
|
||||
return fieldType.cast(value);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't find the field we wanted. As a last gasp attempt, try to find the value on a delegate.
|
||||
if (!fieldName.equals("delegate")) {
|
||||
Object delegate = readFieldOrNull(instance, Object.class, "delegate");
|
||||
if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +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 okhttp3.internal;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import okhttp3.Route;
|
||||
|
||||
/**
|
||||
* 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 or proxy server, that failure is remembered and alternate routes are
|
||||
* preferred.
|
||||
*/
|
||||
public final class RouteDatabase {
|
||||
private final Set<Route> failedRoutes = new LinkedHashSet<>();
|
||||
|
||||
/** Records a failure connecting to {@code failedRoute}. */
|
||||
public synchronized void failed(Route failedRoute) {
|
||||
failedRoutes.add(failedRoute);
|
||||
}
|
||||
|
||||
/** 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();
|
||||
}
|
||||
}
|
||||
@@ -1,449 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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 okhttp3.internal;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.net.IDN;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
import okhttp3.HttpUrl;
|
||||
import okio.Buffer;
|
||||
import okio.ByteString;
|
||||
import okio.Source;
|
||||
|
||||
/** Junk drawer of utility methods. */
|
||||
public final class Util {
|
||||
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||
public static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
/** A cheap and type-safe constant for the UTF-8 Charset. */
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
/** GMT and UTC are equivalent for our purposes. */
|
||||
public static final TimeZone UTC = TimeZone.getTimeZone("GMT");
|
||||
|
||||
/**
|
||||
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
|
||||
* of Android's private InetAddress#isNumeric API.
|
||||
*
|
||||
* <p>This matches IPv6 addresses as a hex string containing at least one colon, and possibly
|
||||
* including dots after the first colon. It matches IPv4 addresses as strings containing only
|
||||
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
|
||||
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
|
||||
* verification).
|
||||
*/
|
||||
private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile(
|
||||
"([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)");
|
||||
|
||||
private Util() {
|
||||
}
|
||||
|
||||
public static void checkOffsetAndCount(long arrayLength, long offset, long count) {
|
||||
if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if two possibly-null objects are equal. */
|
||||
public static boolean equal(Object a, Object b) {
|
||||
return a == b || (a != null && a.equals(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes {@code closeable}, ignoring any checked exceptions. Does nothing if {@code closeable} is
|
||||
* null.
|
||||
*/
|
||||
public static void closeQuietly(Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes {@code socket}, ignoring any checked exceptions. Does nothing if {@code socket} is
|
||||
* null.
|
||||
*/
|
||||
public static void closeQuietly(Socket socket) {
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (AssertionError e) {
|
||||
if (!isAndroidGetsocknameError(e)) throw e;
|
||||
} catch (RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 the other close and
|
||||
* rethrows the first encountered exception.
|
||||
*/
|
||||
public static void closeAll(Closeable a, Closeable b) throws IOException {
|
||||
Throwable thrown = null;
|
||||
try {
|
||||
a.close();
|
||||
} catch (Throwable e) {
|
||||
thrown = e;
|
||||
}
|
||||
try {
|
||||
b.close();
|
||||
} catch (Throwable e) {
|
||||
if (thrown == null) thrown = e;
|
||||
}
|
||||
if (thrown == null) return;
|
||||
if (thrown instanceof IOException) throw (IOException) thrown;
|
||||
if (thrown instanceof RuntimeException) throw (RuntimeException) thrown;
|
||||
if (thrown instanceof Error) throw (Error) thrown;
|
||||
throw new AssertionError(thrown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to exhaust {@code source}, returning true if successful. This is useful when reading a
|
||||
* complete source is helpful, such as when doing so completes a cache body or frees a socket
|
||||
* connection for reuse.
|
||||
*/
|
||||
public static boolean discard(Source source, int timeout, TimeUnit timeUnit) {
|
||||
try {
|
||||
return skipAll(source, timeout, timeUnit);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads until {@code in} is exhausted or the deadline has been reached. This is careful to not
|
||||
* extend the deadline if one exists already.
|
||||
*/
|
||||
public static boolean skipAll(Source source, int duration, TimeUnit timeUnit) throws IOException {
|
||||
long now = System.nanoTime();
|
||||
long originalDuration = source.timeout().hasDeadline()
|
||||
? source.timeout().deadlineNanoTime() - now
|
||||
: Long.MAX_VALUE;
|
||||
source.timeout().deadlineNanoTime(now + Math.min(originalDuration, timeUnit.toNanos(duration)));
|
||||
try {
|
||||
Buffer skipBuffer = new Buffer();
|
||||
while (source.read(skipBuffer, 2048) != -1) {
|
||||
skipBuffer.clear();
|
||||
}
|
||||
return true; // Success! The source has been exhausted.
|
||||
} catch (InterruptedIOException e) {
|
||||
return false; // We ran out of time before exhausting the source.
|
||||
} finally {
|
||||
if (originalDuration == Long.MAX_VALUE) {
|
||||
source.timeout().clearDeadline();
|
||||
} else {
|
||||
source.timeout().deadlineNanoTime(now + originalDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a 32 character string containing an MD5 hash of {@code s}. */
|
||||
public static String md5Hex(String s) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||
byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8"));
|
||||
return ByteString.of(md5bytes).hex();
|
||||
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a Base 64-encoded string containing a SHA-1 hash of {@code s}. */
|
||||
public static String shaBase64(String s) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
|
||||
byte[] sha1Bytes = messageDigest.digest(s.getBytes("UTF-8"));
|
||||
return ByteString.of(sha1Bytes).base64();
|
||||
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a SHA-1 hash of {@code s}. */
|
||||
public static ByteString sha1(ByteString s) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
|
||||
byte[] sha1Bytes = messageDigest.digest(s.toByteArray());
|
||||
return ByteString.of(sha1Bytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a SHA-256 hash of {@code s}. */
|
||||
public static ByteString sha256(ByteString s) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] sha1Bytes = messageDigest.digest(s.toByteArray());
|
||||
return ByteString.of(sha1Bytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns an immutable copy of {@code list}. */
|
||||
public static <T> List<T> immutableList(List<T> list) {
|
||||
return Collections.unmodifiableList(new ArrayList<>(list));
|
||||
}
|
||||
|
||||
/** Returns an immutable list containing {@code elements}. */
|
||||
public static <T> List<T> immutableList(T... elements) {
|
||||
return Collections.unmodifiableList(Arrays.asList(elements.clone()));
|
||||
}
|
||||
|
||||
/** Returns an immutable copy of {@code map}. */
|
||||
public static <K, V> Map<K, V> immutableMap(Map<K, V> map) {
|
||||
return Collections.unmodifiableMap(new LinkedHashMap<>(map));
|
||||
}
|
||||
|
||||
public static ThreadFactory threadFactory(final String name, final boolean daemon) {
|
||||
return new ThreadFactory() {
|
||||
@Override public Thread newThread(Runnable runnable) {
|
||||
Thread result = new Thread(runnable, name);
|
||||
result.setDaemon(daemon);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing containing only elements found in {@code first} and also in {@code
|
||||
* second}. The returned elements are in the same order as in {@code first}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] intersect(Class<T> arrayType, T[] first, T[] second) {
|
||||
List<T> result = intersect(first, second);
|
||||
return result.toArray((T[]) Array.newInstance(arrayType, result.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list containing containing only elements found in {@code first} and also in {@code
|
||||
* second}. The returned elements are in the same order as in {@code first}.
|
||||
*/
|
||||
private static <T> List<T> intersect(T[] first, T[] second) {
|
||||
List<T> result = new ArrayList<>();
|
||||
for (T a : first) {
|
||||
for (T b : second) {
|
||||
if (a.equals(b)) {
|
||||
result.add(b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String hostHeader(HttpUrl url, boolean includeDefaultPort) {
|
||||
String host = url.host().contains(":")
|
||||
? "[" + url.host() + "]"
|
||||
: url.host();
|
||||
return includeDefaultPort || url.port() != HttpUrl.defaultPort(url.scheme())
|
||||
? host + ":" + url.port()
|
||||
: host;
|
||||
}
|
||||
|
||||
/** Returns {@code s} with control characters and non-ASCII characters replaced with '?'. */
|
||||
public static String toHumanReadableAscii(String s) {
|
||||
for (int i = 0, length = s.length(), c; i < length; i += Character.charCount(c)) {
|
||||
c = s.codePointAt(i);
|
||||
if (c > '\u001f' && c < '\u007f') continue;
|
||||
|
||||
Buffer buffer = new Buffer();
|
||||
buffer.writeUtf8(s, 0, i);
|
||||
for (int j = i; j < length; j += Character.charCount(c)) {
|
||||
c = s.codePointAt(j);
|
||||
buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?');
|
||||
}
|
||||
return buffer.readUtf8();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
|
||||
* https://code.google.com/p/android/issues/detail?id=54072
|
||||
*/
|
||||
public static boolean isAndroidGetsocknameError(AssertionError e) {
|
||||
return e.getCause() != null && e.getMessage() != null
|
||||
&& e.getMessage().contains("getsockname failed");
|
||||
}
|
||||
|
||||
public static boolean contains(String[] array, String value) {
|
||||
return Arrays.asList(array).contains(value);
|
||||
}
|
||||
|
||||
public static String[] concat(String[] array, String value) {
|
||||
String[] result = new String[array.length + 1];
|
||||
System.arraycopy(array, 0, result, 0, array.length);
|
||||
result[result.length - 1] = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments {@code pos} until {@code input[pos]} is not ASCII whitespace. Stops at {@code
|
||||
* limit}.
|
||||
*/
|
||||
public static int skipLeadingAsciiWhitespace(String input, int pos, int limit) {
|
||||
for (int i = pos; i < limit; i++) {
|
||||
switch (input.charAt(i)) {
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\f':
|
||||
case '\r':
|
||||
case ' ':
|
||||
continue;
|
||||
default:
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements {@code limit} until {@code input[limit - 1]} is not ASCII whitespace. Stops at
|
||||
* {@code pos}.
|
||||
*/
|
||||
public static int skipTrailingAsciiWhitespace(String input, int pos, int limit) {
|
||||
for (int i = limit - 1; i >= pos; i--) {
|
||||
switch (input.charAt(i)) {
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\f':
|
||||
case '\r':
|
||||
case ' ':
|
||||
continue;
|
||||
default:
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
/** Equivalent to {@code string.substring(pos, limit).trim()}. */
|
||||
public static String trimSubstring(String string, int pos, int limit) {
|
||||
int start = skipLeadingAsciiWhitespace(string, pos, limit);
|
||||
int end = skipTrailingAsciiWhitespace(string, start, limit);
|
||||
return string.substring(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the first character in {@code input} that contains a character in {@code
|
||||
* delimiters}. Returns limit if there is no such character.
|
||||
*/
|
||||
public static int delimiterOffset(String input, int pos, int limit, String delimiters) {
|
||||
for (int i = pos; i < limit; i++) {
|
||||
if (delimiters.indexOf(input.charAt(i)) != -1) return i;
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the first character in {@code input} that is {@code delimiter}. Returns
|
||||
* limit if there is no such character.
|
||||
*/
|
||||
public static int delimiterOffset(String input, int pos, int limit, char delimiter) {
|
||||
for (int i = pos; i < limit; i++) {
|
||||
if (input.charAt(i) == delimiter) return i;
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs IDN ToASCII encoding and canonicalize the result to lowercase. e.g. This converts
|
||||
* {@code ☃.net} to {@code xn--n3h.net}, and {@code WwW.GoOgLe.cOm} to {@code www.google.com}.
|
||||
* {@code null} will be returned if the input cannot be ToASCII encoded or if the result
|
||||
* contains unsupported ASCII characters.
|
||||
*/
|
||||
public static String domainToAscii(String input) {
|
||||
try {
|
||||
String result = IDN.toASCII(input).toLowerCase(Locale.US);
|
||||
if (result.isEmpty()) return null;
|
||||
|
||||
// Confirm that the IDN ToASCII result doesn't contain any illegal characters.
|
||||
if (containsInvalidHostnameAsciiCodes(result)) {
|
||||
return null;
|
||||
}
|
||||
// TODO: implement all label limits.
|
||||
return result;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean containsInvalidHostnameAsciiCodes(String hostnameAscii) {
|
||||
for (int i = 0; i < hostnameAscii.length(); i++) {
|
||||
char c = hostnameAscii.charAt(i);
|
||||
// The WHATWG Host parsing rules accepts some character codes which are invalid by
|
||||
// definition for OkHttp's host header checks (and the WHATWG Host syntax definition). Here
|
||||
// we rule out characters that would cause problems in host headers.
|
||||
if (c <= '\u001f' || c >= '\u007f') {
|
||||
return true;
|
||||
}
|
||||
// Check for the characters mentioned in the WHATWG Host parsing spec:
|
||||
// U+0000, U+0009, U+000A, U+000D, U+0020, "#", "%", "/", ":", "?", "@", "[", "\", and "]"
|
||||
// (excluding the characters covered above).
|
||||
if (" #%/:?@[\\]".indexOf(c) != -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns true if {@code host} is not a host name and might be an IP address. */
|
||||
public static boolean verifyAsIpAddress(String host) {
|
||||
return VERIFY_AS_IP_ADDRESS.matcher(host).matches();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3.internal;
|
||||
|
||||
public final class Version {
|
||||
public static String userAgent() {
|
||||
return "okhttp/3.2.0";
|
||||
}
|
||||
|
||||
private Version() {
|
||||
}
|
||||
}
|
||||
@@ -1,91 +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 okhttp3.internal.framed;
|
||||
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-7
|
||||
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),
|
||||
|
||||
CONNECT_ERROR(10, -1, -1),
|
||||
|
||||
ENHANCE_YOUR_CALM(11, -1, -1),
|
||||
|
||||
INADEQUATE_SECURITY(12, -1, -1),
|
||||
|
||||
HTTP_1_1_REQUIRED(13, -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;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* 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 okhttp3.internal.framed;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import okio.BufferedSource;
|
||||
import okio.ByteString;
|
||||
|
||||
/** Reads transport frames for SPDY/3 or HTTP/2. */
|
||||
public interface FrameReader extends Closeable {
|
||||
void readConnectionPreface() throws IOException;
|
||||
|
||||
boolean nextFrame(Handler handler) throws IOException;
|
||||
|
||||
interface Handler {
|
||||
void data(boolean inFinished, int streamId, BufferedSource source, 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 HEADERS and
|
||||
* PUSH_PROMISE.
|
||||
*
|
||||
* @param outFinished true if the receiver should not send further frames.
|
||||
* @param inFinished true if the sender will not send further frames.
|
||||
* @param streamId the stream owning these headers.
|
||||
* @param associatedStreamId the stream that triggered the sender to create this stream.
|
||||
*/
|
||||
void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
|
||||
List<Header> headerBlock, HeadersMode headersMode);
|
||||
|
||||
void rstStream(int streamId, ErrorCode errorCode);
|
||||
|
||||
void settings(boolean clearPrevious, Settings settings);
|
||||
|
||||
/** HTTP/2 only. */
|
||||
void ackSettings();
|
||||
|
||||
/**
|
||||
* Read a connection-level ping from the peer. {@code ack} indicates this is a reply. Payload
|
||||
* parameters are different between SPDY/3 and HTTP/2.
|
||||
*
|
||||
* <p>In SPDY/3, only the first {@code payload1} parameter is set. If the reader is a client,
|
||||
* it is an unsigned even number. Likewise, a server will receive an odd number.
|
||||
*
|
||||
* <p>In HTTP/2, both {@code payload1} and {@code payload2} parameters are set. The data is
|
||||
* opaque binary, and there are no rules on the content.
|
||||
*/
|
||||
void ping(boolean ack, int payload1, int payload2);
|
||||
|
||||
/**
|
||||
* The peer tells us to stop creating streams. It is safe to replay streams with {@code ID >
|
||||
* lastGoodStreamId} on a new connection. In- flight streams with {@code ID <=
|
||||
* lastGoodStreamId} can only be replayed on a new connection if they are idempotent.
|
||||
*
|
||||
* @param lastGoodStreamId the last stream ID the peer processed before sending this message. If
|
||||
* {@code lastGoodStreamId} is zero, the peer processed no frames.
|
||||
* @param errorCode reason for closing the connection.
|
||||
* @param debugData only valid for HTTP/2; opaque debug data to send.
|
||||
*/
|
||||
void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData);
|
||||
|
||||
/**
|
||||
* Notifies that an additional {@code windowSizeIncrement} bytes can be sent on {@code
|
||||
* streamId}, or the connection if {@code streamId} is zero.
|
||||
*/
|
||||
void windowUpdate(int streamId, long windowSizeIncrement);
|
||||
|
||||
/**
|
||||
* Called when reading a headers or priority frame. This may be used to change the stream's
|
||||
* weight from the default (16) to a new value.
|
||||
*
|
||||
* @param streamId stream which has a priority change.
|
||||
* @param streamDependency the stream ID this stream is dependent on.
|
||||
* @param weight relative proportion of priority in [1..256].
|
||||
* @param exclusive inserts this stream ID as the sole child of {@code streamDependency}.
|
||||
*/
|
||||
void priority(int streamId, int streamDependency, int weight, boolean exclusive);
|
||||
|
||||
/**
|
||||
* HTTP/2 only. Receive a push promise header block. <p> A push promise contains all the headers
|
||||
* that pertain to a server-initiated request, and a {@code promisedStreamId} to which response
|
||||
* frames will be delivered. Push promise frames are sent as a part of the response to {@code
|
||||
* streamId}.
|
||||
*
|
||||
* @param streamId client-initiated stream ID. Must be an odd number.
|
||||
* @param promisedStreamId server-initiated stream ID. Must be an even number.
|
||||
* @param requestHeaders minimally includes {@code :method}, {@code :scheme}, {@code
|
||||
* :authority}, and (@code :path}.
|
||||
*/
|
||||
void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* HTTP/2 only. Expresses that resources for the connection or a client- initiated stream are
|
||||
* available from a different network location or protocol configuration.
|
||||
*
|
||||
* <p>See <a href="http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-01">alt-svc</a>
|
||||
*
|
||||
* @param streamId when a client-initiated stream ID (odd number), the origin of this alternate
|
||||
* service is the origin of the stream. When zero, the origin is specified in the {@code origin}
|
||||
* parameter.
|
||||
* @param origin when present, the <a href="http://tools.ietf.org/html/rfc6454">origin</a> is
|
||||
* typically represented as a combination of scheme, host and port. When empty, the origin is
|
||||
* that of the {@code streamId}.
|
||||
* @param protocol an ALPN protocol, such as {@code h2}.
|
||||
* @param host an IP address or hostname.
|
||||
* @param port the IP port associated with the service.
|
||||
* @param maxAge time in seconds that this alternative is considered fresh.
|
||||
*/
|
||||
void alternateService(int streamId, String origin, ByteString protocol, String host, int port,
|
||||
long maxAge);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* 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 okhttp3.internal.framed;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import okio.Buffer;
|
||||
|
||||
/** Writes transport frames for SPDY/3 or HTTP/2. */
|
||||
public interface FrameWriter extends Closeable {
|
||||
/** HTTP/2 only. */
|
||||
void connectionPreface() throws IOException;
|
||||
|
||||
/** Informs the peer that we've applied its latest settings. */
|
||||
void ackSettings(Settings peerSettings) throws IOException;
|
||||
|
||||
/**
|
||||
* HTTP/2 only. Send a push promise header block.
|
||||
*
|
||||
* <p>A push promise contains all the headers that pertain to a server-initiated request, and a
|
||||
* {@code promisedStreamId} to which response frames will be delivered. Push promise frames are
|
||||
* sent as a part of the response to {@code streamId}. The {@code promisedStreamId} has a priority
|
||||
* of one greater than {@code streamId}.
|
||||
*
|
||||
* @param streamId client-initiated stream ID. Must be an odd number.
|
||||
* @param promisedStreamId server-initiated stream ID. Must be an even number.
|
||||
* @param requestHeaders minimally includes {@code :method}, {@code :scheme}, {@code :authority},
|
||||
* and (@code :path}.
|
||||
*/
|
||||
void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
|
||||
throws IOException;
|
||||
|
||||
/** SPDY/3 only. */
|
||||
void flush() throws IOException;
|
||||
|
||||
void synStream(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
|
||||
List<Header> headerBlock) throws IOException;
|
||||
|
||||
void synReply(boolean outFinished, int streamId, List<Header> headerBlock)
|
||||
throws IOException;
|
||||
|
||||
void headers(int streamId, List<Header> headerBlock) throws IOException;
|
||||
|
||||
void rstStream(int streamId, ErrorCode errorCode) throws IOException;
|
||||
|
||||
/** The maximum size of bytes that may be sent in a single call to {@link #data}. */
|
||||
int maxDataLength();
|
||||
|
||||
/**
|
||||
* {@code source.length} may be longer than the max length of the variant's data frame.
|
||||
* Implementations must send multiple frames as necessary.
|
||||
*
|
||||
* @param source the buffer to draw bytes from. May be null if byteCount is 0.
|
||||
* @param byteCount must be between 0 and the minimum of {code source.length} and {@link
|
||||
* #maxDataLength}.
|
||||
*/
|
||||
void data(boolean outFinished, int streamId, Buffer source, int byteCount) throws IOException;
|
||||
|
||||
/** Write okhttp's settings to the peer. */
|
||||
void settings(Settings okHttpSettings) throws IOException;
|
||||
|
||||
/**
|
||||
* Send a connection-level ping to the peer. {@code ack} indicates this is a reply. Payload
|
||||
* parameters are different between SPDY/3 and HTTP/2.
|
||||
*
|
||||
* <p>In SPDY/3, only the first {@code payload1} parameter is sent. If the sender is a client, it
|
||||
* is an unsigned odd number. Likewise, a server will send an even number.
|
||||
*
|
||||
* <p>In HTTP/2, both {@code payload1} and {@code payload2} parameters are sent. The data is
|
||||
* opaque binary, and there are no rules on the content.
|
||||
*/
|
||||
void ping(boolean ack, int payload1, int payload2) throws IOException;
|
||||
|
||||
/**
|
||||
* Tell the peer to stop creating streams and that we last processed {@code lastGoodStreamId}, or
|
||||
* zero if no streams were processed.
|
||||
*
|
||||
* @param lastGoodStreamId the last stream ID processed, or zero if no streams were processed.
|
||||
* @param errorCode reason for closing the connection.
|
||||
* @param debugData only valid for HTTP/2; opaque debug data to send.
|
||||
*/
|
||||
void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) throws IOException;
|
||||
|
||||
/**
|
||||
* Inform peer that an additional {@code windowSizeIncrement} bytes can be sent on {@code
|
||||
* streamId}, or the connection if {@code streamId} is zero.
|
||||
*/
|
||||
void windowUpdate(int streamId, long windowSizeIncrement) throws IOException;
|
||||
}
|
||||
@@ -1,934 +0,0 @@
|
||||
/*
|
||||
* 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 okhttp3.internal.framed;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import okhttp3.Protocol;
|
||||
import okhttp3.internal.NamedRunnable;
|
||||
import okhttp3.internal.Util;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSink;
|
||||
import okio.BufferedSource;
|
||||
import okio.ByteString;
|
||||
import okio.Okio;
|
||||
|
||||
import static okhttp3.internal.Internal.logger;
|
||||
import static okhttp3.internal.framed.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
|
||||
|
||||
/**
|
||||
* A socket connection to a remote peer. A connection hosts streams which can send and receive
|
||||
* data.
|
||||
*
|
||||
* <p>Many methods in this API are <strong>synchronous:</strong> the call is completed before the
|
||||
* method returns. This is typical for Java but atypical for SPDY. This is motivated by exception
|
||||
* transparency: an IOException that was triggered by a certain caller can be caught and handled by
|
||||
* that caller.
|
||||
*/
|
||||
public final class FramedConnection implements Closeable {
|
||||
|
||||
// Internal state of this connection is guarded by 'this'. No blocking
|
||||
// operations may be performed while holding this lock!
|
||||
//
|
||||
// Socket writes are guarded by frameWriter.
|
||||
//
|
||||
// Socket reads are unguarded but are only made by the reader thread.
|
||||
//
|
||||
// Certain operations (like SYN_STREAM) need to synchronize on both the
|
||||
// frameWriter (to do blocking I/O) and this (to create streams). Such
|
||||
// operations must synchronize on 'this' last. This ensures that we never
|
||||
// wait for a blocking operation while holding 'this'.
|
||||
|
||||
private static final ExecutorService executor = new ThreadPoolExecutor(0,
|
||||
Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
|
||||
Util.threadFactory("OkHttp FramedConnection", true));
|
||||
|
||||
/** The protocol variant, like {@link Spdy3}. */
|
||||
final Protocol protocol;
|
||||
|
||||
/** True if this peer initiated the connection. */
|
||||
final boolean client;
|
||||
|
||||
/**
|
||||
* User code to run in response to incoming streams or settings. Calls to this are always invoked
|
||||
* on {@link #executor}.
|
||||
*/
|
||||
private final Listener listener;
|
||||
private final Map<Integer, FramedStream> streams = new HashMap<>();
|
||||
private final String hostname;
|
||||
private int lastGoodStreamId;
|
||||
private int nextStreamId;
|
||||
private boolean shutdown;
|
||||
private long idleStartTimeNs = System.nanoTime();
|
||||
|
||||
/** Ensures push promise callbacks events are sent in order per stream. */
|
||||
private final ExecutorService pushExecutor;
|
||||
|
||||
/** Lazily-created map of in-flight pings awaiting a response. Guarded by this. */
|
||||
private Map<Integer, Ping> pings;
|
||||
/** User code to run in response to push promise events. */
|
||||
private final PushObserver pushObserver;
|
||||
private int nextPingId;
|
||||
|
||||
/**
|
||||
* The total number of bytes consumed by the application, but not yet acknowledged by sending a
|
||||
* {@code WINDOW_UPDATE} frame on this connection.
|
||||
*/
|
||||
// Visible for testing
|
||||
long unacknowledgedBytesRead = 0;
|
||||
|
||||
/**
|
||||
* Count of bytes that can be written on the connection before receiving a window update.
|
||||
*/
|
||||
// Visible for testing
|
||||
long bytesLeftInWriteWindow;
|
||||
|
||||
/** Settings we communicate to the peer. */
|
||||
Settings okHttpSettings = new Settings();
|
||||
|
||||
private static final int OKHTTP_CLIENT_WINDOW_SIZE = 16 * 1024 * 1024;
|
||||
|
||||
/** Settings we receive from the peer. */
|
||||
// TODO: MWS will need to guard on this setting before attempting to push.
|
||||
final Settings peerSettings = new Settings();
|
||||
|
||||
private boolean receivedInitialPeerSettings = false;
|
||||
final Variant variant;
|
||||
final Socket socket;
|
||||
final FrameWriter frameWriter;
|
||||
|
||||
// Visible for testing
|
||||
final Reader readerRunnable;
|
||||
|
||||
private FramedConnection(Builder builder) throws IOException {
|
||||
protocol = builder.protocol;
|
||||
pushObserver = builder.pushObserver;
|
||||
client = builder.client;
|
||||
listener = builder.listener;
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-5.1.1
|
||||
nextStreamId = builder.client ? 1 : 2;
|
||||
if (builder.client && protocol == Protocol.HTTP_2) {
|
||||
nextStreamId += 2; // In HTTP/2, 1 on client is reserved for Upgrade.
|
||||
}
|
||||
|
||||
nextPingId = builder.client ? 1 : 2;
|
||||
|
||||
// Flow control was designed more for servers, or proxies than edge clients.
|
||||
// If we are a client, set the flow control window to 16MiB. This avoids
|
||||
// thrashing window updates every 64KiB, yet small enough to avoid blowing
|
||||
// up the heap.
|
||||
if (builder.client) {
|
||||
okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, OKHTTP_CLIENT_WINDOW_SIZE);
|
||||
}
|
||||
|
||||
hostname = builder.hostname;
|
||||
|
||||
if (protocol == Protocol.HTTP_2) {
|
||||
variant = new Http2();
|
||||
// Like newSingleThreadExecutor, except lazy creates the thread.
|
||||
pushExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
Util.threadFactory(String.format("OkHttp %s Push Observer", hostname), true));
|
||||
// 1 less than SPDY http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-6.9.2
|
||||
peerSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, 65535);
|
||||
peerSettings.set(Settings.MAX_FRAME_SIZE, 0, Http2.INITIAL_MAX_FRAME_SIZE);
|
||||
} else if (protocol == Protocol.SPDY_3) {
|
||||
variant = new Spdy3();
|
||||
pushExecutor = null;
|
||||
} else {
|
||||
throw new AssertionError(protocol);
|
||||
}
|
||||
bytesLeftInWriteWindow = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
|
||||
socket = builder.socket;
|
||||
frameWriter = variant.newWriter(builder.sink, client);
|
||||
|
||||
readerRunnable = new Reader(variant.newReader(builder.source, client));
|
||||
new Thread(readerRunnable).start(); // Not a daemon thread.
|
||||
}
|
||||
|
||||
/** The protocol as selected using ALPN. */
|
||||
public Protocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of {@link FramedStream#isOpen() open streams} on this connection.
|
||||
*/
|
||||
public synchronized int openStreamCount() {
|
||||
return streams.size();
|
||||
}
|
||||
|
||||
synchronized FramedStream getStream(int id) {
|
||||
return streams.get(id);
|
||||
}
|
||||
|
||||
synchronized FramedStream removeStream(int streamId) {
|
||||
FramedStream stream = streams.remove(streamId);
|
||||
if (stream != null && streams.isEmpty()) {
|
||||
setIdle(true);
|
||||
}
|
||||
notifyAll(); // The removed stream may be blocked on a connection-wide window update.
|
||||
return stream;
|
||||
}
|
||||
|
||||
private synchronized void setIdle(boolean value) {
|
||||
idleStartTimeNs = value ? System.nanoTime() : Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
/** Returns true if this connection is idle. */
|
||||
public synchronized boolean isIdle() {
|
||||
return idleStartTimeNs != Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
public synchronized int maxConcurrentStreams() {
|
||||
return peerSettings.getMaxConcurrentStreams(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time in ns when this connection became idle or Long.MAX_VALUE if connection is not
|
||||
* idle.
|
||||
*/
|
||||
public synchronized long getIdleStartTimeNs() {
|
||||
return idleStartTimeNs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new server-initiated stream.
|
||||
*
|
||||
* @param associatedStreamId the stream that triggered the sender to create this stream.
|
||||
* @param out true to create an output stream that we can use to send data to the remote peer.
|
||||
* Corresponds to {@code FLAG_FIN}.
|
||||
*/
|
||||
public FramedStream pushStream(int associatedStreamId, List<Header> requestHeaders, boolean out)
|
||||
throws IOException {
|
||||
if (client) throw new IllegalStateException("Client cannot push requests.");
|
||||
if (protocol != Protocol.HTTP_2) throw new IllegalStateException("protocol != HTTP_2");
|
||||
return newStream(associatedStreamId, requestHeaders, out, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new locally-initiated stream.
|
||||
*
|
||||
* @param out true to create an output stream that we can use to send data to the remote peer.
|
||||
* Corresponds to {@code FLAG_FIN}.
|
||||
* @param in true to create an input stream that the remote peer can use to send data to us.
|
||||
* Corresponds to {@code FLAG_UNIDIRECTIONAL}.
|
||||
*/
|
||||
public FramedStream newStream(List<Header> requestHeaders, boolean out, boolean in)
|
||||
throws IOException {
|
||||
return newStream(0, requestHeaders, out, in);
|
||||
}
|
||||
|
||||
private FramedStream newStream(int associatedStreamId, List<Header> requestHeaders, boolean out,
|
||||
boolean in) throws IOException {
|
||||
boolean outFinished = !out;
|
||||
boolean inFinished = !in;
|
||||
FramedStream stream;
|
||||
int streamId;
|
||||
|
||||
synchronized (frameWriter) {
|
||||
synchronized (this) {
|
||||
if (shutdown) {
|
||||
throw new IOException("shutdown");
|
||||
}
|
||||
streamId = nextStreamId;
|
||||
nextStreamId += 2;
|
||||
stream = new FramedStream(streamId, this, outFinished, inFinished, requestHeaders);
|
||||
if (stream.isOpen()) {
|
||||
streams.put(streamId, stream);
|
||||
setIdle(false);
|
||||
}
|
||||
}
|
||||
if (associatedStreamId == 0) {
|
||||
frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId,
|
||||
requestHeaders);
|
||||
} else if (client) {
|
||||
throw new IllegalArgumentException("client streams shouldn't have associated stream IDs");
|
||||
} else { // HTTP/2 has a PUSH_PROMISE frame.
|
||||
frameWriter.pushPromise(associatedStreamId, streamId, requestHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
if (!out) {
|
||||
frameWriter.flush();
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
void writeSynReply(int streamId, boolean outFinished, List<Header> alternating)
|
||||
throws IOException {
|
||||
frameWriter.synReply(outFinished, streamId, alternating);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callers of this method are not thread safe, and sometimes on application threads. Most often,
|
||||
* this method will be called to send a buffer worth of data to the peer.
|
||||
*
|
||||
* <p>Writes are subject to the write window of the stream and the connection. Until there is a
|
||||
* window sufficient to send {@code byteCount}, the caller will block. For example, a user of
|
||||
* {@code HttpURLConnection} who flushes more bytes to the output stream than the connection's
|
||||
* write window will block.
|
||||
*
|
||||
* <p>Zero {@code byteCount} writes are not subject to flow control and will not block. The only
|
||||
* use case for zero {@code byteCount} is closing a flushed output stream.
|
||||
*/
|
||||
public void writeData(int streamId, boolean outFinished, Buffer buffer, long byteCount)
|
||||
throws IOException {
|
||||
if (byteCount == 0) { // Empty data frames are not flow-controlled.
|
||||
frameWriter.data(outFinished, streamId, buffer, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
while (byteCount > 0) {
|
||||
int toWrite;
|
||||
synchronized (FramedConnection.this) {
|
||||
try {
|
||||
while (bytesLeftInWriteWindow <= 0) {
|
||||
// Before blocking, confirm that the stream we're writing is still open. It's possible
|
||||
// that the stream has since been closed (such as if this write timed out.)
|
||||
if (!streams.containsKey(streamId)) {
|
||||
throw new IOException("stream closed");
|
||||
}
|
||||
FramedConnection.this.wait(); // Wait until we receive a WINDOW_UPDATE.
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new InterruptedIOException();
|
||||
}
|
||||
|
||||
toWrite = (int) Math.min(byteCount, bytesLeftInWriteWindow);
|
||||
toWrite = Math.min(toWrite, frameWriter.maxDataLength());
|
||||
bytesLeftInWriteWindow -= toWrite;
|
||||
}
|
||||
|
||||
byteCount -= toWrite;
|
||||
frameWriter.data(outFinished && byteCount == 0, streamId, buffer, toWrite);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code delta} will be negative if a settings frame initial window is smaller than the last.
|
||||
*/
|
||||
void addBytesToWriteWindow(long delta) {
|
||||
bytesLeftInWriteWindow += delta;
|
||||
if (delta > 0) FramedConnection.this.notifyAll();
|
||||
}
|
||||
|
||||
void writeSynResetLater(final int streamId, final ErrorCode errorCode) {
|
||||
executor.submit(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
writeSynReset(streamId, errorCode);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void writeSynReset(int streamId, ErrorCode statusCode) throws IOException {
|
||||
frameWriter.rstStream(streamId, statusCode);
|
||||
}
|
||||
|
||||
void writeWindowUpdateLater(final int streamId, final long unacknowledgedBytesRead) {
|
||||
executor.execute(new NamedRunnable("OkHttp Window Update %s stream %d", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
frameWriter.windowUpdate(streamId, unacknowledgedBytesRead);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a ping frame to the peer. Use the returned object to await the ping's response and
|
||||
* observe its round trip time.
|
||||
*/
|
||||
public Ping ping() throws IOException {
|
||||
Ping ping = new Ping();
|
||||
int pingId;
|
||||
synchronized (this) {
|
||||
if (shutdown) {
|
||||
throw new IOException("shutdown");
|
||||
}
|
||||
pingId = nextPingId;
|
||||
nextPingId += 2;
|
||||
if (pings == null) pings = new HashMap<>();
|
||||
pings.put(pingId, ping);
|
||||
}
|
||||
writePing(false, pingId, 0x4f4b6f6b /* ASCII "OKok" */, ping);
|
||||
return ping;
|
||||
}
|
||||
|
||||
private void writePingLater(
|
||||
final boolean reply, final int payload1, final int payload2, final Ping ping) {
|
||||
executor.execute(new NamedRunnable("OkHttp %s ping %08x%08x",
|
||||
hostname, payload1, payload2) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
writePing(reply, payload1, payload2, ping);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void writePing(boolean reply, int payload1, int payload2, Ping ping) throws IOException {
|
||||
synchronized (frameWriter) {
|
||||
// Observe the sent time immediately before performing I/O.
|
||||
if (ping != null) ping.send();
|
||||
frameWriter.ping(reply, payload1, payload2);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized Ping removePing(int id) {
|
||||
return pings != null ? pings.remove(id) : null;
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
frameWriter.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Degrades this connection such that new streams can neither be created locally, nor accepted
|
||||
* from the remote peer. Existing streams are not impacted. This is intended to permit an endpoint
|
||||
* to gracefully stop accepting new requests without harming previously established streams.
|
||||
*/
|
||||
public void shutdown(ErrorCode statusCode) throws IOException {
|
||||
synchronized (frameWriter) {
|
||||
int lastGoodStreamId;
|
||||
synchronized (this) {
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
shutdown = true;
|
||||
lastGoodStreamId = this.lastGoodStreamId;
|
||||
}
|
||||
// TODO: propagate exception message into debugData
|
||||
frameWriter.goAway(lastGoodStreamId, statusCode, Util.EMPTY_BYTE_ARRAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this connection. This cancels all open streams and unanswered pings. It closes the
|
||||
* underlying input and output streams and shuts down internal executor services.
|
||||
*/
|
||||
@Override public void close() throws IOException {
|
||||
close(ErrorCode.NO_ERROR, ErrorCode.CANCEL);
|
||||
}
|
||||
|
||||
private void close(ErrorCode connectionCode, ErrorCode streamCode) throws IOException {
|
||||
assert (!Thread.holdsLock(this));
|
||||
IOException thrown = null;
|
||||
try {
|
||||
shutdown(connectionCode);
|
||||
} catch (IOException e) {
|
||||
thrown = e;
|
||||
}
|
||||
|
||||
FramedStream[] streamsToClose = null;
|
||||
Ping[] pingsToCancel = null;
|
||||
synchronized (this) {
|
||||
if (!streams.isEmpty()) {
|
||||
streamsToClose = streams.values().toArray(new FramedStream[streams.size()]);
|
||||
streams.clear();
|
||||
setIdle(false);
|
||||
}
|
||||
if (pings != null) {
|
||||
pingsToCancel = pings.values().toArray(new Ping[pings.size()]);
|
||||
pings = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamsToClose != null) {
|
||||
for (FramedStream stream : streamsToClose) {
|
||||
try {
|
||||
stream.close(streamCode);
|
||||
} catch (IOException e) {
|
||||
if (thrown != null) thrown = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pingsToCancel != null) {
|
||||
for (Ping ping : pingsToCancel) {
|
||||
ping.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Close the writer to release its resources (such as deflaters).
|
||||
try {
|
||||
frameWriter.close();
|
||||
} catch (IOException e) {
|
||||
if (thrown == null) thrown = e;
|
||||
}
|
||||
|
||||
// Close the socket to break out the reader thread, which will clean up after itself.
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
thrown = e;
|
||||
}
|
||||
|
||||
if (thrown != null) throw thrown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a connection header if the current variant requires it. This should be called after
|
||||
* {@link Builder#build} for all new connections.
|
||||
*/
|
||||
public void sendConnectionPreface() throws IOException {
|
||||
frameWriter.connectionPreface();
|
||||
frameWriter.settings(okHttpSettings);
|
||||
int windowSize = okHttpSettings.getInitialWindowSize(Settings.DEFAULT_INITIAL_WINDOW_SIZE);
|
||||
if (windowSize != Settings.DEFAULT_INITIAL_WINDOW_SIZE) {
|
||||
frameWriter.windowUpdate(0, windowSize - Settings.DEFAULT_INITIAL_WINDOW_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges {@code settings} into this peer's settings and sends them to the remote peer. */
|
||||
public void setSettings(Settings settings) throws IOException {
|
||||
synchronized (frameWriter) {
|
||||
synchronized (this) {
|
||||
if (shutdown) {
|
||||
throw new IOException("shutdown");
|
||||
}
|
||||
okHttpSettings.merge(settings);
|
||||
frameWriter.settings(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Socket socket;
|
||||
private String hostname;
|
||||
private BufferedSource source;
|
||||
private BufferedSink sink;
|
||||
private Listener listener = Listener.REFUSE_INCOMING_STREAMS;
|
||||
private Protocol protocol = Protocol.SPDY_3;
|
||||
private PushObserver pushObserver = PushObserver.CANCEL;
|
||||
private boolean client;
|
||||
|
||||
/**
|
||||
* @param client true if this peer initiated the connection; false if this peer accepted the
|
||||
* connection.
|
||||
*/
|
||||
public Builder(boolean client) throws IOException {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public Builder socket(Socket socket) throws IOException {
|
||||
return socket(socket, ((InetSocketAddress) socket.getRemoteSocketAddress()).getHostName(),
|
||||
Okio.buffer(Okio.source(socket)), Okio.buffer(Okio.sink(socket)));
|
||||
}
|
||||
|
||||
public Builder socket(
|
||||
Socket socket, String hostname, BufferedSource source, BufferedSink sink) {
|
||||
this.socket = socket;
|
||||
this.hostname = hostname;
|
||||
this.source = source;
|
||||
this.sink = sink;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder listener(Listener listener) {
|
||||
this.listener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder protocol(Protocol protocol) {
|
||||
this.protocol = protocol;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder pushObserver(PushObserver pushObserver) {
|
||||
this.pushObserver = pushObserver;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FramedConnection build() throws IOException {
|
||||
return new FramedConnection(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods in this class must not lock FrameWriter. If a method needs to write a frame, create an
|
||||
* async task to do so.
|
||||
*/
|
||||
class Reader extends NamedRunnable implements FrameReader.Handler {
|
||||
final FrameReader frameReader;
|
||||
|
||||
private Reader(FrameReader frameReader) {
|
||||
super("OkHttp %s", hostname);
|
||||
this.frameReader = frameReader;
|
||||
}
|
||||
|
||||
@Override protected void execute() {
|
||||
ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR;
|
||||
ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR;
|
||||
try {
|
||||
if (!client) {
|
||||
frameReader.readConnectionPreface();
|
||||
}
|
||||
while (frameReader.nextFrame(this)) {
|
||||
}
|
||||
connectionErrorCode = ErrorCode.NO_ERROR;
|
||||
streamErrorCode = ErrorCode.CANCEL;
|
||||
} catch (IOException e) {
|
||||
connectionErrorCode = ErrorCode.PROTOCOL_ERROR;
|
||||
streamErrorCode = ErrorCode.PROTOCOL_ERROR;
|
||||
} finally {
|
||||
try {
|
||||
close(connectionErrorCode, streamErrorCode);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
Util.closeQuietly(frameReader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void data(boolean inFinished, int streamId, BufferedSource source, int length)
|
||||
throws IOException {
|
||||
if (pushedStream(streamId)) {
|
||||
pushDataLater(streamId, source, length, inFinished);
|
||||
return;
|
||||
}
|
||||
FramedStream dataStream = getStream(streamId);
|
||||
if (dataStream == null) {
|
||||
writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
|
||||
source.skip(length);
|
||||
return;
|
||||
}
|
||||
dataStream.receiveData(source, length);
|
||||
if (inFinished) {
|
||||
dataStream.receiveFin();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void headers(boolean outFinished, boolean inFinished, int streamId,
|
||||
int associatedStreamId, List<Header> headerBlock, HeadersMode headersMode) {
|
||||
if (pushedStream(streamId)) {
|
||||
pushHeadersLater(streamId, headerBlock, inFinished);
|
||||
return;
|
||||
}
|
||||
FramedStream stream;
|
||||
synchronized (FramedConnection.this) {
|
||||
// If we're shutdown, don't bother with this stream.
|
||||
if (shutdown) return;
|
||||
|
||||
stream = getStream(streamId);
|
||||
|
||||
if (stream == null) {
|
||||
// The headers claim to be for an existing stream, but we don't have one.
|
||||
if (headersMode.failIfStreamAbsent()) {
|
||||
writeSynResetLater(streamId, ErrorCode.INVALID_STREAM);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the stream ID is less than the last created ID, assume it's already closed.
|
||||
if (streamId <= lastGoodStreamId) return;
|
||||
|
||||
// If the stream ID is in the client's namespace, assume it's already closed.
|
||||
if (streamId % 2 == nextStreamId % 2) return;
|
||||
|
||||
// Create a stream.
|
||||
final FramedStream
|
||||
newStream = new FramedStream(streamId, FramedConnection.this, outFinished,
|
||||
inFinished, headerBlock);
|
||||
lastGoodStreamId = streamId;
|
||||
streams.put(streamId, newStream);
|
||||
executor.execute(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
listener.onStream(newStream);
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.INFO, "FramedConnection.Listener failure for " + hostname, e);
|
||||
try {
|
||||
newStream.close(ErrorCode.PROTOCOL_ERROR);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The headers claim to be for a new stream, but we already have one.
|
||||
if (headersMode.failIfStreamPresent()) {
|
||||
stream.closeLater(ErrorCode.PROTOCOL_ERROR);
|
||||
removeStream(streamId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update an existing stream.
|
||||
stream.receiveHeaders(headerBlock, headersMode);
|
||||
if (inFinished) stream.receiveFin();
|
||||
}
|
||||
|
||||
@Override public void rstStream(int streamId, ErrorCode errorCode) {
|
||||
if (pushedStream(streamId)) {
|
||||
pushResetLater(streamId, errorCode);
|
||||
return;
|
||||
}
|
||||
FramedStream rstStream = removeStream(streamId);
|
||||
if (rstStream != null) {
|
||||
rstStream.receiveRstStream(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void settings(boolean clearPrevious, Settings newSettings) {
|
||||
long delta = 0;
|
||||
FramedStream[] streamsToNotify = null;
|
||||
synchronized (FramedConnection.this) {
|
||||
int priorWriteWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
|
||||
if (clearPrevious) peerSettings.clear();
|
||||
peerSettings.merge(newSettings);
|
||||
if (getProtocol() == Protocol.HTTP_2) {
|
||||
ackSettingsLater(newSettings);
|
||||
}
|
||||
int peerInitialWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
|
||||
if (peerInitialWindowSize != -1 && peerInitialWindowSize != priorWriteWindowSize) {
|
||||
delta = peerInitialWindowSize - priorWriteWindowSize;
|
||||
if (!receivedInitialPeerSettings) {
|
||||
addBytesToWriteWindow(delta);
|
||||
receivedInitialPeerSettings = true;
|
||||
}
|
||||
if (!streams.isEmpty()) {
|
||||
streamsToNotify = streams.values().toArray(new FramedStream[streams.size()]);
|
||||
}
|
||||
}
|
||||
executor.execute(new NamedRunnable("OkHttp %s settings", hostname) {
|
||||
@Override public void execute() {
|
||||
listener.onSettings(FramedConnection.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (streamsToNotify != null && delta != 0) {
|
||||
for (FramedStream stream : streamsToNotify) {
|
||||
synchronized (stream) {
|
||||
stream.addBytesToWriteWindow(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ackSettingsLater(final Settings peerSettings) {
|
||||
executor.execute(new NamedRunnable("OkHttp %s ACK Settings", hostname) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
frameWriter.ackSettings(peerSettings);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void ackSettings() {
|
||||
// TODO: If we don't get this callback after sending settings to the peer, SETTINGS_TIMEOUT.
|
||||
}
|
||||
|
||||
@Override public void ping(boolean reply, int payload1, int payload2) {
|
||||
if (reply) {
|
||||
Ping ping = removePing(payload1);
|
||||
if (ping != null) {
|
||||
ping.receive();
|
||||
}
|
||||
} else {
|
||||
// Send a reply to a client ping if this is a server and vice versa.
|
||||
writePingLater(true, payload1, payload2, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
|
||||
if (debugData.size() > 0) { // TODO: log the debugData
|
||||
}
|
||||
|
||||
// Copy the streams first. We don't want to hold a lock when we call receiveRstStream().
|
||||
FramedStream[] streamsCopy;
|
||||
synchronized (FramedConnection.this) {
|
||||
streamsCopy = streams.values().toArray(new FramedStream[streams.size()]);
|
||||
shutdown = true;
|
||||
}
|
||||
|
||||
// Fail all streams created after the last good stream ID.
|
||||
for (FramedStream framedStream : streamsCopy) {
|
||||
if (framedStream.getId() > lastGoodStreamId && framedStream.isLocallyInitiated()) {
|
||||
framedStream.receiveRstStream(ErrorCode.REFUSED_STREAM);
|
||||
removeStream(framedStream.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void windowUpdate(int streamId, long windowSizeIncrement) {
|
||||
if (streamId == 0) {
|
||||
synchronized (FramedConnection.this) {
|
||||
bytesLeftInWriteWindow += windowSizeIncrement;
|
||||
FramedConnection.this.notifyAll();
|
||||
}
|
||||
} else {
|
||||
FramedStream stream = getStream(streamId);
|
||||
if (stream != null) {
|
||||
synchronized (stream) {
|
||||
stream.addBytesToWriteWindow(windowSizeIncrement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void priority(int streamId, int streamDependency, int weight,
|
||||
boolean exclusive) {
|
||||
// TODO: honor priority.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders) {
|
||||
pushRequestLater(promisedStreamId, requestHeaders);
|
||||
}
|
||||
|
||||
@Override public void alternateService(int streamId, String origin, ByteString protocol,
|
||||
String host, int port, long maxAge) {
|
||||
// TODO: register alternate service.
|
||||
}
|
||||
}
|
||||
|
||||
/** Even, positive numbered streams are pushed streams in HTTP/2. */
|
||||
private boolean pushedStream(int streamId) {
|
||||
return protocol == Protocol.HTTP_2 && streamId != 0 && (streamId & 1) == 0;
|
||||
}
|
||||
|
||||
// Guarded by this.
|
||||
private final Set<Integer> currentPushRequests = new LinkedHashSet<>();
|
||||
|
||||
private void pushRequestLater(final int streamId, final List<Header> requestHeaders) {
|
||||
synchronized (this) {
|
||||
if (currentPushRequests.contains(streamId)) {
|
||||
writeSynResetLater(streamId, ErrorCode.PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
currentPushRequests.add(streamId);
|
||||
}
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Request[%s]", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
boolean cancel = pushObserver.onRequest(streamId, requestHeaders);
|
||||
try {
|
||||
if (cancel) {
|
||||
frameWriter.rstStream(streamId, ErrorCode.CANCEL);
|
||||
synchronized (FramedConnection.this) {
|
||||
currentPushRequests.remove(streamId);
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void pushHeadersLater(final int streamId, final List<Header> requestHeaders,
|
||||
final boolean inFinished) {
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Headers[%s]", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
boolean cancel = pushObserver.onHeaders(streamId, requestHeaders, inFinished);
|
||||
try {
|
||||
if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL);
|
||||
if (cancel || inFinished) {
|
||||
synchronized (FramedConnection.this) {
|
||||
currentPushRequests.remove(streamId);
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly reads {@code byteCount} bytes from the source before launching a background task to
|
||||
* process the data. This avoids corrupting the stream.
|
||||
*/
|
||||
private void pushDataLater(final int streamId, final BufferedSource source, final int byteCount,
|
||||
final boolean inFinished) throws IOException {
|
||||
final Buffer buffer = new Buffer();
|
||||
source.require(byteCount); // Eagerly read the frame before firing client thread.
|
||||
source.read(buffer, byteCount);
|
||||
if (buffer.size() != byteCount) throw new IOException(buffer.size() + " != " + byteCount);
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Data[%s]", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
try {
|
||||
boolean cancel = pushObserver.onData(streamId, buffer, byteCount, inFinished);
|
||||
if (cancel) frameWriter.rstStream(streamId, ErrorCode.CANCEL);
|
||||
if (cancel || inFinished) {
|
||||
synchronized (FramedConnection.this) {
|
||||
currentPushRequests.remove(streamId);
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void pushResetLater(final int streamId, final ErrorCode errorCode) {
|
||||
pushExecutor.execute(new NamedRunnable("OkHttp %s Push Reset[%s]", hostname, streamId) {
|
||||
@Override public void execute() {
|
||||
pushObserver.onReset(streamId, errorCode);
|
||||
synchronized (FramedConnection.this) {
|
||||
currentPushRequests.remove(streamId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Listener of streams and settings initiated by the peer. */
|
||||
public abstract static class Listener {
|
||||
public static final Listener REFUSE_INCOMING_STREAMS = new Listener() {
|
||||
@Override public void onStream(FramedStream stream) throws IOException {
|
||||
stream.close(ErrorCode.REFUSED_STREAM);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a new stream from this connection's peer. Implementations should respond by either
|
||||
* {@linkplain FramedStream#reply replying to the stream} or {@linkplain FramedStream#close
|
||||
* closing it}. This response does not need to be synchronous.
|
||||
*/
|
||||
public abstract void onStream(FramedStream stream) throws IOException;
|
||||
|
||||
/**
|
||||
* Notification that the connection's peer's settings may have changed. Implementations should
|
||||
* take appropriate action to handle the updated settings.
|
||||
*
|
||||
* <p>It is the implementation's responsibility to handle concurrent calls to this method. A
|
||||
* remote peer that sends multiple settings frames will trigger multiple calls to this method,
|
||||
* and those calls are not necessarily serialized.
|
||||
*/
|
||||
public void onSettings(FramedConnection connection) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,611 +0,0 @@
|
||||
/*
|
||||
* 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 okhttp3.internal.framed;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import okio.AsyncTimeout;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import okio.Sink;
|
||||
import okio.Source;
|
||||
import okio.Timeout;
|
||||
|
||||
import static okhttp3.internal.framed.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
|
||||
|
||||
/** A logical bidirectional stream. */
|
||||
public final class FramedStream {
|
||||
// Internal state is guarded by this. No long-running or potentially
|
||||
// blocking operations are performed while the lock is held.
|
||||
|
||||
/**
|
||||
* The total number of bytes consumed by the application (with {@link FramedDataSource#read}), but
|
||||
* not yet acknowledged by sending a {@code WINDOW_UPDATE} frame on this stream.
|
||||
*/
|
||||
// Visible for testing
|
||||
long unacknowledgedBytesRead = 0;
|
||||
|
||||
/**
|
||||
* Count of bytes that can be written on the stream before receiving a window update. Even if this
|
||||
* is positive, writes will block until there available bytes in {@code
|
||||
* connection.bytesLeftInWriteWindow}.
|
||||
*/
|
||||
// guarded by this
|
||||
long bytesLeftInWriteWindow;
|
||||
|
||||
private final int id;
|
||||
private final FramedConnection connection;
|
||||
|
||||
/** Headers sent by the stream initiator. Immutable and non null. */
|
||||
private final List<Header> requestHeaders;
|
||||
|
||||
/** Headers sent in the stream reply. Null if reply is either not sent or not sent yet. */
|
||||
private List<Header> responseHeaders;
|
||||
|
||||
private final FramedDataSource source;
|
||||
final FramedDataSink sink;
|
||||
private final StreamTimeout readTimeout = new StreamTimeout();
|
||||
private final StreamTimeout writeTimeout = new StreamTimeout();
|
||||
|
||||
/**
|
||||
* The reason why this stream was abnormally closed. If there are multiple reasons to abnormally
|
||||
* close this stream (such as both peers closing it near-simultaneously) then this is the first
|
||||
* reason known to this peer.
|
||||
*/
|
||||
private ErrorCode errorCode = null;
|
||||
|
||||
FramedStream(int id, FramedConnection connection, boolean outFinished, boolean inFinished,
|
||||
List<Header> requestHeaders) {
|
||||
if (connection == null) throw new NullPointerException("connection == null");
|
||||
if (requestHeaders == null) throw new NullPointerException("requestHeaders == null");
|
||||
this.id = id;
|
||||
this.connection = connection;
|
||||
this.bytesLeftInWriteWindow =
|
||||
connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
|
||||
this.source = new FramedDataSource(
|
||||
connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE));
|
||||
this.sink = new FramedDataSink();
|
||||
this.source.finished = inFinished;
|
||||
this.sink.finished = outFinished;
|
||||
this.requestHeaders = requestHeaders;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this stream is open. A stream is open until either:
|
||||
*
|
||||
* <ul>
|
||||
* <li>A {@code SYN_RESET} frame abnormally terminates the stream.
|
||||
* <li>Both input and output streams have transmitted all data and headers.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note that the input stream may continue to yield data even after a stream reports itself as
|
||||
* not open. This is because input data is buffered.
|
||||
*/
|
||||
public synchronized boolean isOpen() {
|
||||
if (errorCode != null) {
|
||||
return false;
|
||||
}
|
||||
if ((source.finished || source.closed)
|
||||
&& (sink.finished || sink.closed)
|
||||
&& responseHeaders != null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns true if this stream was created by this peer. */
|
||||
public boolean isLocallyInitiated() {
|
||||
boolean streamIsClient = ((id & 1) == 1);
|
||||
return connection.client == streamIsClient;
|
||||
}
|
||||
|
||||
public FramedConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public List<Header> getRequestHeaders() {
|
||||
return requestHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream's response headers, blocking if necessary if they have not been received
|
||||
* yet.
|
||||
*/
|
||||
public synchronized List<Header> getResponseHeaders() throws IOException {
|
||||
readTimeout.enter();
|
||||
try {
|
||||
while (responseHeaders == null && errorCode == null) {
|
||||
waitForIo();
|
||||
}
|
||||
} finally {
|
||||
readTimeout.exitAndThrowIfTimedOut();
|
||||
}
|
||||
if (responseHeaders != null) return responseHeaders;
|
||||
throw new IOException("stream was reset: " + errorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reason why this stream was closed, or null if it closed normally or has not yet
|
||||
* been closed.
|
||||
*/
|
||||
public synchronized ErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a reply to an incoming stream.
|
||||
*
|
||||
* @param out true to create an output stream that we can use to send data to the remote peer.
|
||||
* Corresponds to {@code FLAG_FIN}.
|
||||
*/
|
||||
public void reply(List<Header> responseHeaders, boolean out) throws IOException {
|
||||
assert (!Thread.holdsLock(FramedStream.this));
|
||||
boolean outFinished = false;
|
||||
synchronized (this) {
|
||||
if (responseHeaders == null) {
|
||||
throw new NullPointerException("responseHeaders == null");
|
||||
}
|
||||
if (this.responseHeaders != null) {
|
||||
throw new IllegalStateException("reply already sent");
|
||||
}
|
||||
this.responseHeaders = responseHeaders;
|
||||
if (!out) {
|
||||
this.sink.finished = true;
|
||||
outFinished = true;
|
||||
}
|
||||
}
|
||||
connection.writeSynReply(id, outFinished, responseHeaders);
|
||||
|
||||
if (outFinished) {
|
||||
connection.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public Timeout readTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
public Timeout writeTimeout() {
|
||||
return writeTimeout;
|
||||
}
|
||||
|
||||
/** Returns a source that reads data from the peer. */
|
||||
public Source getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sink that can be used to write data to the peer.
|
||||
*
|
||||
* @throws IllegalStateException if this stream was initiated by the peer and a {@link #reply} has
|
||||
* not yet been sent.
|
||||
*/
|
||||
public Sink getSink() {
|
||||
synchronized (this) {
|
||||
if (responseHeaders == null && !isLocallyInitiated()) {
|
||||
throw new IllegalStateException("reply before requesting the sink");
|
||||
}
|
||||
}
|
||||
return sink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abnormally terminate this stream. This blocks until the {@code RST_STREAM} frame has been
|
||||
* transmitted.
|
||||
*/
|
||||
public void close(ErrorCode rstStatusCode) throws IOException {
|
||||
if (!closeInternal(rstStatusCode)) {
|
||||
return; // Already closed.
|
||||
}
|
||||
connection.writeSynReset(id, rstStatusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abnormally terminate this stream. This enqueues a {@code RST_STREAM} frame and returns
|
||||
* immediately.
|
||||
*/
|
||||
public void closeLater(ErrorCode errorCode) {
|
||||
if (!closeInternal(errorCode)) {
|
||||
return; // Already closed.
|
||||
}
|
||||
connection.writeSynResetLater(id, errorCode);
|
||||
}
|
||||
|
||||
/** Returns true if this stream was closed. */
|
||||
private boolean closeInternal(ErrorCode errorCode) {
|
||||
assert (!Thread.holdsLock(this));
|
||||
synchronized (this) {
|
||||
if (this.errorCode != null) {
|
||||
return false;
|
||||
}
|
||||
if (source.finished && sink.finished) {
|
||||
return false;
|
||||
}
|
||||
this.errorCode = errorCode;
|
||||
notifyAll();
|
||||
}
|
||||
connection.removeStream(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void receiveHeaders(List<Header> headers, HeadersMode headersMode) {
|
||||
assert (!Thread.holdsLock(FramedStream.this));
|
||||
ErrorCode errorCode = null;
|
||||
boolean open = true;
|
||||
synchronized (this) {
|
||||
if (responseHeaders == null) {
|
||||
if (headersMode.failIfHeadersAbsent()) {
|
||||
errorCode = ErrorCode.PROTOCOL_ERROR;
|
||||
} else {
|
||||
responseHeaders = headers;
|
||||
open = isOpen();
|
||||
notifyAll();
|
||||
}
|
||||
} else {
|
||||
if (headersMode.failIfHeadersPresent()) {
|
||||
errorCode = ErrorCode.STREAM_IN_USE;
|
||||
} else {
|
||||
List<Header> newHeaders = new ArrayList<>();
|
||||
newHeaders.addAll(responseHeaders);
|
||||
newHeaders.addAll(headers);
|
||||
this.responseHeaders = newHeaders;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errorCode != null) {
|
||||
closeLater(errorCode);
|
||||
} else if (!open) {
|
||||
connection.removeStream(id);
|
||||
}
|
||||
}
|
||||
|
||||
void receiveData(BufferedSource in, int length) throws IOException {
|
||||
assert (!Thread.holdsLock(FramedStream.this));
|
||||
this.source.receive(in, length);
|
||||
}
|
||||
|
||||
void receiveFin() {
|
||||
assert (!Thread.holdsLock(FramedStream.this));
|
||||
boolean open;
|
||||
synchronized (this) {
|
||||
this.source.finished = true;
|
||||
open = isOpen();
|
||||
notifyAll();
|
||||
}
|
||||
if (!open) {
|
||||
connection.removeStream(id);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void receiveRstStream(ErrorCode errorCode) {
|
||||
if (this.errorCode == null) {
|
||||
this.errorCode = errorCode;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A source that reads the incoming data frames of a stream. Although this class uses
|
||||
* synchronization to safely receive incoming data frames, it is not intended for use by multiple
|
||||
* readers.
|
||||
*/
|
||||
private final class FramedDataSource implements Source {
|
||||
/** Buffer to receive data from the network into. Only accessed by the reader thread. */
|
||||
private final Buffer receiveBuffer = new Buffer();
|
||||
|
||||
/** Buffer with readable data. Guarded by FramedStream.this. */
|
||||
private final Buffer readBuffer = new Buffer();
|
||||
|
||||
/** Maximum number of bytes to buffer before reporting a flow control error. */
|
||||
private final long maxByteCount;
|
||||
|
||||
/** True if the caller has closed this stream. */
|
||||
private boolean closed;
|
||||
|
||||
/**
|
||||
* True if either side has cleanly shut down this stream. We will receive no more bytes beyond
|
||||
* those already in the buffer.
|
||||
*/
|
||||
private boolean finished;
|
||||
|
||||
private FramedDataSource(long maxByteCount) {
|
||||
this.maxByteCount = maxByteCount;
|
||||
}
|
||||
|
||||
@Override public long read(Buffer sink, long byteCount)
|
||||
throws IOException {
|
||||
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
|
||||
|
||||
long read;
|
||||
synchronized (FramedStream.this) {
|
||||
waitUntilReadable();
|
||||
checkNotClosed();
|
||||
if (readBuffer.size() == 0) return -1; // This source is exhausted.
|
||||
|
||||
// Move bytes from the read buffer into the caller's buffer.
|
||||
read = readBuffer.read(sink, Math.min(byteCount, readBuffer.size()));
|
||||
|
||||
// Flow control: notify the peer that we're ready for more data!
|
||||
unacknowledgedBytesRead += read;
|
||||
if (unacknowledgedBytesRead
|
||||
>= connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) {
|
||||
connection.writeWindowUpdateLater(id, unacknowledgedBytesRead);
|
||||
unacknowledgedBytesRead = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Update connection.unacknowledgedBytesRead outside the stream lock.
|
||||
synchronized (connection) { // Multiple application threads may hit this section.
|
||||
connection.unacknowledgedBytesRead += read;
|
||||
if (connection.unacknowledgedBytesRead
|
||||
>= connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) {
|
||||
connection.writeWindowUpdateLater(0, connection.unacknowledgedBytesRead);
|
||||
connection.unacknowledgedBytesRead = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
/** Returns once the source is either readable or finished. */
|
||||
private void waitUntilReadable() throws IOException {
|
||||
readTimeout.enter();
|
||||
try {
|
||||
while (readBuffer.size() == 0 && !finished && !closed && errorCode == null) {
|
||||
waitForIo();
|
||||
}
|
||||
} finally {
|
||||
readTimeout.exitAndThrowIfTimedOut();
|
||||
}
|
||||
}
|
||||
|
||||
void receive(BufferedSource in, long byteCount) throws IOException {
|
||||
assert (!Thread.holdsLock(FramedStream.this));
|
||||
|
||||
while (byteCount > 0) {
|
||||
boolean finished;
|
||||
boolean flowControlError;
|
||||
synchronized (FramedStream.this) {
|
||||
finished = this.finished;
|
||||
flowControlError = byteCount + readBuffer.size() > maxByteCount;
|
||||
}
|
||||
|
||||
// If the peer sends more data than we can handle, discard it and close the connection.
|
||||
if (flowControlError) {
|
||||
in.skip(byteCount);
|
||||
closeLater(ErrorCode.FLOW_CONTROL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Discard data received after the stream is finished. It's probably a benign race.
|
||||
if (finished) {
|
||||
in.skip(byteCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill the receive buffer without holding any locks.
|
||||
long read = in.read(receiveBuffer, byteCount);
|
||||
if (read == -1) throw new EOFException();
|
||||
byteCount -= read;
|
||||
|
||||
// Move the received data to the read buffer to the reader can read it.
|
||||
synchronized (FramedStream.this) {
|
||||
boolean wasEmpty = readBuffer.size() == 0;
|
||||
readBuffer.writeAll(receiveBuffer);
|
||||
if (wasEmpty) {
|
||||
FramedStream.this.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Timeout timeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
synchronized (FramedStream.this) {
|
||||
closed = true;
|
||||
readBuffer.clear();
|
||||
FramedStream.this.notifyAll();
|
||||
}
|
||||
cancelStreamIfNecessary();
|
||||
}
|
||||
|
||||
private void checkNotClosed() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException("stream closed");
|
||||
}
|
||||
if (errorCode != null) {
|
||||
throw new IOException("stream was reset: " + errorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelStreamIfNecessary() throws IOException {
|
||||
assert (!Thread.holdsLock(FramedStream.this));
|
||||
boolean open;
|
||||
boolean cancel;
|
||||
synchronized (this) {
|
||||
cancel = !source.finished && source.closed && (sink.finished || sink.closed);
|
||||
open = isOpen();
|
||||
}
|
||||
if (cancel) {
|
||||
// RST this stream to prevent additional data from being sent. This
|
||||
// is safe because the input stream is closed (we won't use any
|
||||
// further bytes) and the output stream is either finished or closed
|
||||
// (so RSTing both streams doesn't cause harm).
|
||||
FramedStream.this.close(ErrorCode.CANCEL);
|
||||
} else if (!open) {
|
||||
connection.removeStream(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink that writes outgoing data frames of a stream. This class is not thread safe.
|
||||
*/
|
||||
final class FramedDataSink implements Sink {
|
||||
private static final long EMIT_BUFFER_SIZE = 16384;
|
||||
|
||||
/**
|
||||
* Buffer of outgoing data. This batches writes of small writes into this sink as larges frames
|
||||
* written to the outgoing connection. Batching saves the (small) framing overhead.
|
||||
*/
|
||||
private final Buffer sendBuffer = new Buffer();
|
||||
|
||||
private boolean closed;
|
||||
|
||||
/**
|
||||
* True if either side has cleanly shut down this stream. We shall send no more bytes.
|
||||
*/
|
||||
private boolean finished;
|
||||
|
||||
@Override public void write(Buffer source, long byteCount) throws IOException {
|
||||
assert (!Thread.holdsLock(FramedStream.this));
|
||||
sendBuffer.write(source, byteCount);
|
||||
while (sendBuffer.size() >= EMIT_BUFFER_SIZE) {
|
||||
emitDataFrame(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a single data frame to the connection. The frame's size be limited by this stream's
|
||||
* write window. This method will block until the write window is nonempty.
|
||||
*/
|
||||
private void emitDataFrame(boolean outFinished) throws IOException {
|
||||
long toWrite;
|
||||
synchronized (FramedStream.this) {
|
||||
writeTimeout.enter();
|
||||
try {
|
||||
while (bytesLeftInWriteWindow <= 0 && !finished && !closed && errorCode == null) {
|
||||
waitForIo(); // Wait until we receive a WINDOW_UPDATE for this stream.
|
||||
}
|
||||
} finally {
|
||||
writeTimeout.exitAndThrowIfTimedOut();
|
||||
}
|
||||
|
||||
checkOutNotClosed(); // Kick out if the stream was reset or closed while waiting.
|
||||
toWrite = Math.min(bytesLeftInWriteWindow, sendBuffer.size());
|
||||
bytesLeftInWriteWindow -= toWrite;
|
||||
}
|
||||
|
||||
writeTimeout.enter();
|
||||
try {
|
||||
connection.writeData(id, outFinished && toWrite == sendBuffer.size(), sendBuffer, toWrite);
|
||||
} finally {
|
||||
writeTimeout.exitAndThrowIfTimedOut();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void flush() throws IOException {
|
||||
assert (!Thread.holdsLock(FramedStream.this));
|
||||
synchronized (FramedStream.this) {
|
||||
checkOutNotClosed();
|
||||
}
|
||||
while (sendBuffer.size() > 0) {
|
||||
emitDataFrame(false);
|
||||
connection.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Timeout timeout() {
|
||||
return writeTimeout;
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
assert (!Thread.holdsLock(FramedStream.this));
|
||||
synchronized (FramedStream.this) {
|
||||
if (closed) return;
|
||||
}
|
||||
if (!sink.finished) {
|
||||
// Emit the remaining data, setting the END_STREAM flag on the last frame.
|
||||
if (sendBuffer.size() > 0) {
|
||||
while (sendBuffer.size() > 0) {
|
||||
emitDataFrame(true);
|
||||
}
|
||||
} else {
|
||||
// Send an empty frame just so we can set the END_STREAM flag.
|
||||
connection.writeData(id, true, null, 0);
|
||||
}
|
||||
}
|
||||
synchronized (FramedStream.this) {
|
||||
closed = true;
|
||||
}
|
||||
connection.flush();
|
||||
cancelStreamIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code delta} will be negative if a settings frame initial window is smaller than the last.
|
||||
*/
|
||||
void addBytesToWriteWindow(long delta) {
|
||||
bytesLeftInWriteWindow += delta;
|
||||
if (delta > 0) FramedStream.this.notifyAll();
|
||||
}
|
||||
|
||||
private void checkOutNotClosed() throws IOException {
|
||||
if (sink.closed) {
|
||||
throw new IOException("stream closed");
|
||||
} else if (sink.finished) {
|
||||
throw new IOException("stream finished");
|
||||
} else if (errorCode != null) {
|
||||
throw new IOException("stream was reset: " + errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #wait}, but throws an {@code InterruptedIOException} when interrupted instead of
|
||||
* the more awkward {@link InterruptedException}.
|
||||
*/
|
||||
private void waitForIo() throws InterruptedIOException {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
throw new InterruptedIOException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Okio timeout watchdog will call {@link #timedOut} if the timeout is reached. In that case
|
||||
* we close the stream (asynchronously) which will notify the waiting thread.
|
||||
*/
|
||||
class StreamTimeout extends AsyncTimeout {
|
||||
@Override protected void timedOut() {
|
||||
closeLater(ErrorCode.CANCEL);
|
||||
}
|
||||
|
||||
@Override protected IOException newTimeoutException(IOException cause) {
|
||||
SocketTimeoutException socketTimeoutException = new SocketTimeoutException("timeout");
|
||||
if (cause != null) {
|
||||
socketTimeoutException.initCause(cause);
|
||||
}
|
||||
return socketTimeoutException;
|
||||
}
|
||||
|
||||
public void exitAndThrowIfTimedOut() throws IOException {
|
||||
if (exit()) throw newTimeoutException(null /* cause */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package okhttp3.internal.framed;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
/** HTTP header: the name is an ASCII string, but the value can be UTF-8. */
|
||||
public final class Header {
|
||||
// Special header names defined in the SPDY and HTTP/2 specs.
|
||||
public static final ByteString RESPONSE_STATUS = ByteString.encodeUtf8(":status");
|
||||
public static final ByteString TARGET_METHOD = ByteString.encodeUtf8(":method");
|
||||
public static final ByteString TARGET_PATH = ByteString.encodeUtf8(":path");
|
||||
public static final ByteString TARGET_SCHEME = ByteString.encodeUtf8(":scheme");
|
||||
public static final ByteString TARGET_AUTHORITY = ByteString.encodeUtf8(":authority"); // HTTP/2
|
||||
public static final ByteString TARGET_HOST = ByteString.encodeUtf8(":host"); // spdy/3
|
||||
public static final ByteString VERSION = ByteString.encodeUtf8(":version"); // spdy/3
|
||||
|
||||
/** Name in case-insensitive ASCII encoding. */
|
||||
public final ByteString name;
|
||||
/** Value in UTF-8 encoding. */
|
||||
public final ByteString value;
|
||||
final int hpackSize;
|
||||
|
||||
// TODO: search for toLowerCase and consider moving logic here.
|
||||
public Header(String name, String value) {
|
||||
this(ByteString.encodeUtf8(name), ByteString.encodeUtf8(value));
|
||||
}
|
||||
|
||||
public Header(ByteString name, String value) {
|
||||
this(name, ByteString.encodeUtf8(value));
|
||||
}
|
||||
|
||||
public Header(ByteString name, ByteString value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.hpackSize = 32 + name.size() + value.size();
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object other) {
|
||||
if (other instanceof Header) {
|
||||
Header that = (Header) other;
|
||||
return this.name.equals(that.name)
|
||||
&& this.value.equals(that.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + name.hashCode();
|
||||
result = 31 * result + value.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return String.format("%s: %s", name.utf8(), value.utf8());
|
||||
}
|
||||
}
|
||||
@@ -1,47 +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 okhttp3.internal.framed;
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
@@ -1,435 +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 okhttp3.internal.framed;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import okio.ByteString;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
/**
|
||||
* Read and write HPACK v10.
|
||||
*
|
||||
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12
|
||||
*
|
||||
* This implementation uses an array for the dynamic table and a list for indexed entries. Dynamic
|
||||
* entries are added to the array, starting in the last position moving forward. When the array
|
||||
* fills, it is doubled.
|
||||
*/
|
||||
final class Hpack {
|
||||
private static final int PREFIX_4_BITS = 0x0f;
|
||||
private static final int PREFIX_5_BITS = 0x1f;
|
||||
private static final int PREFIX_6_BITS = 0x3f;
|
||||
private static final int PREFIX_7_BITS = 0x7f;
|
||||
|
||||
private static final Header[] STATIC_HEADER_TABLE = new Header[] {
|
||||
new Header(Header.TARGET_AUTHORITY, ""),
|
||||
new Header(Header.TARGET_METHOD, "GET"),
|
||||
new Header(Header.TARGET_METHOD, "POST"),
|
||||
new Header(Header.TARGET_PATH, "/"),
|
||||
new Header(Header.TARGET_PATH, "/index.html"),
|
||||
new Header(Header.TARGET_SCHEME, "http"),
|
||||
new Header(Header.TARGET_SCHEME, "https"),
|
||||
new Header(Header.RESPONSE_STATUS, "200"),
|
||||
new Header(Header.RESPONSE_STATUS, "204"),
|
||||
new Header(Header.RESPONSE_STATUS, "206"),
|
||||
new Header(Header.RESPONSE_STATUS, "304"),
|
||||
new Header(Header.RESPONSE_STATUS, "400"),
|
||||
new Header(Header.RESPONSE_STATUS, "404"),
|
||||
new Header(Header.RESPONSE_STATUS, "500"),
|
||||
new Header("accept-charset", ""),
|
||||
new Header("accept-encoding", "gzip, deflate"),
|
||||
new Header("accept-language", ""),
|
||||
new Header("accept-ranges", ""),
|
||||
new Header("accept", ""),
|
||||
new Header("access-control-allow-origin", ""),
|
||||
new Header("age", ""),
|
||||
new Header("allow", ""),
|
||||
new Header("authorization", ""),
|
||||
new Header("cache-control", ""),
|
||||
new Header("content-disposition", ""),
|
||||
new Header("content-encoding", ""),
|
||||
new Header("content-language", ""),
|
||||
new Header("content-length", ""),
|
||||
new Header("content-location", ""),
|
||||
new Header("content-range", ""),
|
||||
new Header("content-type", ""),
|
||||
new Header("cookie", ""),
|
||||
new Header("date", ""),
|
||||
new Header("etag", ""),
|
||||
new Header("expect", ""),
|
||||
new Header("expires", ""),
|
||||
new Header("from", ""),
|
||||
new Header("host", ""),
|
||||
new Header("if-match", ""),
|
||||
new Header("if-modified-since", ""),
|
||||
new Header("if-none-match", ""),
|
||||
new Header("if-range", ""),
|
||||
new Header("if-unmodified-since", ""),
|
||||
new Header("last-modified", ""),
|
||||
new Header("link", ""),
|
||||
new Header("location", ""),
|
||||
new Header("max-forwards", ""),
|
||||
new Header("proxy-authenticate", ""),
|
||||
new Header("proxy-authorization", ""),
|
||||
new Header("range", ""),
|
||||
new Header("referer", ""),
|
||||
new Header("refresh", ""),
|
||||
new Header("retry-after", ""),
|
||||
new Header("server", ""),
|
||||
new Header("set-cookie", ""),
|
||||
new Header("strict-transport-security", ""),
|
||||
new Header("transfer-encoding", ""),
|
||||
new Header("user-agent", ""),
|
||||
new Header("vary", ""),
|
||||
new Header("via", ""),
|
||||
new Header("www-authenticate", "")
|
||||
};
|
||||
|
||||
private Hpack() {
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-3.1
|
||||
static final class Reader {
|
||||
|
||||
private final List<Header> headerList = new ArrayList<>();
|
||||
private final BufferedSource source;
|
||||
|
||||
private int headerTableSizeSetting;
|
||||
private int maxDynamicTableByteCount;
|
||||
// Visible for testing.
|
||||
Header[] dynamicTable = new Header[8];
|
||||
// Array is populated back to front, so new entries always have lowest index.
|
||||
int nextHeaderIndex = dynamicTable.length - 1;
|
||||
int headerCount = 0;
|
||||
int dynamicTableByteCount = 0;
|
||||
|
||||
Reader(int headerTableSizeSetting, Source source) {
|
||||
this.headerTableSizeSetting = headerTableSizeSetting;
|
||||
this.maxDynamicTableByteCount = headerTableSizeSetting;
|
||||
this.source = Okio.buffer(source);
|
||||
}
|
||||
|
||||
int maxDynamicTableByteCount() {
|
||||
return maxDynamicTableByteCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the reader when the peer sent {@link Settings#HEADER_TABLE_SIZE}. While this
|
||||
* establishes the maximum dynamic table size, the {@link #maxDynamicTableByteCount} set during
|
||||
* processing may limit the table size to a smaller amount.
|
||||
*
|
||||
* <p>Evicts entries or clears the table as needed.
|
||||
*/
|
||||
void headerTableSizeSetting(int headerTableSizeSetting) {
|
||||
this.headerTableSizeSetting = headerTableSizeSetting;
|
||||
this.maxDynamicTableByteCount = headerTableSizeSetting;
|
||||
adjustDynamicTableByteCount();
|
||||
}
|
||||
|
||||
private void adjustDynamicTableByteCount() {
|
||||
if (maxDynamicTableByteCount < dynamicTableByteCount) {
|
||||
if (maxDynamicTableByteCount == 0) {
|
||||
clearDynamicTable();
|
||||
} else {
|
||||
evictToRecoverBytes(dynamicTableByteCount - maxDynamicTableByteCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDynamicTable() {
|
||||
headerList.clear();
|
||||
Arrays.fill(dynamicTable, null);
|
||||
nextHeaderIndex = dynamicTable.length - 1;
|
||||
headerCount = 0;
|
||||
dynamicTableByteCount = 0;
|
||||
}
|
||||
|
||||
/** Returns the count of entries evicted. */
|
||||
private int evictToRecoverBytes(int bytesToRecover) {
|
||||
int entriesToEvict = 0;
|
||||
if (bytesToRecover > 0) {
|
||||
// determine how many headers need to be evicted.
|
||||
for (int j = dynamicTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) {
|
||||
bytesToRecover -= dynamicTable[j].hpackSize;
|
||||
dynamicTableByteCount -= dynamicTable[j].hpackSize;
|
||||
headerCount--;
|
||||
entriesToEvict++;
|
||||
}
|
||||
System.arraycopy(dynamicTable, nextHeaderIndex + 1, dynamicTable,
|
||||
nextHeaderIndex + 1 + entriesToEvict, headerCount);
|
||||
nextHeaderIndex += entriesToEvict;
|
||||
}
|
||||
return entriesToEvict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read {@code byteCount} bytes of headers from the source stream. This implementation does not
|
||||
* propagate the never indexed flag of a header.
|
||||
*/
|
||||
void readHeaders() throws IOException {
|
||||
while (!source.exhausted()) {
|
||||
int b = source.readByte() & 0xff;
|
||||
if (b == 0x80) { // 10000000
|
||||
throw new IOException("index == 0");
|
||||
} else if ((b & 0x80) == 0x80) { // 1NNNNNNN
|
||||
int index = readInt(b, PREFIX_7_BITS);
|
||||
readIndexedHeader(index - 1);
|
||||
} else if (b == 0x40) { // 01000000
|
||||
readLiteralHeaderWithIncrementalIndexingNewName();
|
||||
} else if ((b & 0x40) == 0x40) { // 01NNNNNN
|
||||
int index = readInt(b, PREFIX_6_BITS);
|
||||
readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
|
||||
} else if ((b & 0x20) == 0x20) { // 001NNNNN
|
||||
maxDynamicTableByteCount = readInt(b, PREFIX_5_BITS);
|
||||
if (maxDynamicTableByteCount < 0
|
||||
|| maxDynamicTableByteCount > headerTableSizeSetting) {
|
||||
throw new IOException("Invalid dynamic table size update " + maxDynamicTableByteCount);
|
||||
}
|
||||
adjustDynamicTableByteCount();
|
||||
} else if (b == 0x10 || b == 0) { // 000?0000 - Ignore never indexed bit.
|
||||
readLiteralHeaderWithoutIndexingNewName();
|
||||
} else { // 000?NNNN - Ignore never indexed bit.
|
||||
int index = readInt(b, PREFIX_4_BITS);
|
||||
readLiteralHeaderWithoutIndexingIndexedName(index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Header> getAndResetHeaderList() {
|
||||
List<Header> result = new ArrayList<>(headerList);
|
||||
headerList.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void readIndexedHeader(int index) throws IOException {
|
||||
if (isStaticHeader(index)) {
|
||||
Header staticEntry = STATIC_HEADER_TABLE[index];
|
||||
headerList.add(staticEntry);
|
||||
} else {
|
||||
int dynamicTableIndex = dynamicTableIndex(index - STATIC_HEADER_TABLE.length);
|
||||
if (dynamicTableIndex < 0 || dynamicTableIndex > dynamicTable.length - 1) {
|
||||
throw new IOException("Header index too large " + (index + 1));
|
||||
}
|
||||
headerList.add(dynamicTable[dynamicTableIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
// referencedHeaders is relative to nextHeaderIndex + 1.
|
||||
private int dynamicTableIndex(int index) {
|
||||
return nextHeaderIndex + 1 + index;
|
||||
}
|
||||
|
||||
private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException {
|
||||
ByteString name = getName(index);
|
||||
ByteString value = readByteString();
|
||||
headerList.add(new Header(name, value));
|
||||
}
|
||||
|
||||
private void readLiteralHeaderWithoutIndexingNewName() throws IOException {
|
||||
ByteString name = checkLowercase(readByteString());
|
||||
ByteString value = readByteString();
|
||||
headerList.add(new Header(name, value));
|
||||
}
|
||||
|
||||
private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)
|
||||
throws IOException {
|
||||
ByteString name = getName(nameIndex);
|
||||
ByteString value = readByteString();
|
||||
insertIntoDynamicTable(-1, new Header(name, value));
|
||||
}
|
||||
|
||||
private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
|
||||
ByteString name = checkLowercase(readByteString());
|
||||
ByteString value = readByteString();
|
||||
insertIntoDynamicTable(-1, new Header(name, value));
|
||||
}
|
||||
|
||||
private ByteString getName(int index) {
|
||||
if (isStaticHeader(index)) {
|
||||
return STATIC_HEADER_TABLE[index].name;
|
||||
} else {
|
||||
return dynamicTable[dynamicTableIndex(index - STATIC_HEADER_TABLE.length)].name;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStaticHeader(int index) {
|
||||
return index >= 0 && index <= STATIC_HEADER_TABLE.length - 1;
|
||||
}
|
||||
|
||||
/** index == -1 when new. */
|
||||
private void insertIntoDynamicTable(int index, Header entry) {
|
||||
headerList.add(entry);
|
||||
|
||||
int delta = entry.hpackSize;
|
||||
if (index != -1) { // Index -1 == new header.
|
||||
delta -= dynamicTable[dynamicTableIndex(index)].hpackSize;
|
||||
}
|
||||
|
||||
// if the new or replacement header is too big, drop all entries.
|
||||
if (delta > maxDynamicTableByteCount) {
|
||||
clearDynamicTable();
|
||||
return;
|
||||
}
|
||||
|
||||
// Evict headers to the required length.
|
||||
int bytesToRecover = (dynamicTableByteCount + delta) - maxDynamicTableByteCount;
|
||||
int entriesEvicted = evictToRecoverBytes(bytesToRecover);
|
||||
|
||||
if (index == -1) { // Adding a value to the dynamic table.
|
||||
if (headerCount + 1 > dynamicTable.length) { // Need to grow the dynamic table.
|
||||
Header[] doubled = new Header[dynamicTable.length * 2];
|
||||
System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length);
|
||||
nextHeaderIndex = dynamicTable.length - 1;
|
||||
dynamicTable = doubled;
|
||||
}
|
||||
index = nextHeaderIndex--;
|
||||
dynamicTable[index] = entry;
|
||||
headerCount++;
|
||||
} else { // Replace value at same position.
|
||||
index += dynamicTableIndex(index) + entriesEvicted;
|
||||
dynamicTable[index] = entry;
|
||||
}
|
||||
dynamicTableByteCount += delta;
|
||||
}
|
||||
|
||||
private int readByte() throws IOException {
|
||||
return source.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 potentially Huffman encoded byte string. */
|
||||
ByteString readByteString() throws IOException {
|
||||
int firstByte = readByte();
|
||||
boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNN
|
||||
int length = readInt(firstByte, PREFIX_7_BITS);
|
||||
|
||||
if (huffmanDecode) {
|
||||
return ByteString.of(Huffman.get().decode(source.readByteArray(length)));
|
||||
} else {
|
||||
return source.readByteString(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<ByteString, Integer> NAME_TO_FIRST_INDEX = nameToFirstIndex();
|
||||
|
||||
private static Map<ByteString, Integer> nameToFirstIndex() {
|
||||
Map<ByteString, Integer> result = new LinkedHashMap<>(STATIC_HEADER_TABLE.length);
|
||||
for (int i = 0; i < STATIC_HEADER_TABLE.length; i++) {
|
||||
if (!result.containsKey(STATIC_HEADER_TABLE[i].name)) {
|
||||
result.put(STATIC_HEADER_TABLE[i].name, i);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
static final class Writer {
|
||||
private final Buffer out;
|
||||
|
||||
Writer(Buffer out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/** This does not use "never indexed" semantics for sensitive headers. */
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-6.2.3
|
||||
void writeHeaders(List<Header> headerBlock) throws IOException {
|
||||
// TODO: implement index tracking
|
||||
for (int i = 0, size = headerBlock.size(); i < size; i++) {
|
||||
ByteString name = headerBlock.get(i).name.toAsciiLowercase();
|
||||
Integer staticIndex = NAME_TO_FIRST_INDEX.get(name);
|
||||
if (staticIndex != null) {
|
||||
// Literal Header Field without Indexing - Indexed Name.
|
||||
writeInt(staticIndex + 1, PREFIX_4_BITS, 0);
|
||||
writeByteString(headerBlock.get(i).value);
|
||||
} else {
|
||||
out.writeByte(0x00); // Literal Header without Indexing - New Name.
|
||||
writeByteString(name);
|
||||
writeByteString(headerBlock.get(i).value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1
|
||||
void writeInt(int value, int prefixMask, int bits) throws IOException {
|
||||
// Write the raw value for a single byte value.
|
||||
if (value < prefixMask) {
|
||||
out.writeByte(bits | value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the mask to start a multibyte value.
|
||||
out.writeByte(bits | prefixMask);
|
||||
value -= prefixMask;
|
||||
|
||||
// Write 7 bits at a time 'til we're done.
|
||||
while (value >= 0x80) {
|
||||
int b = value & 0x7f;
|
||||
out.writeByte(b | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
out.writeByte(value);
|
||||
}
|
||||
|
||||
void writeByteString(ByteString data) throws IOException {
|
||||
writeInt(data.size(), PREFIX_7_BITS, 0);
|
||||
out.write(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An HTTP/2 response cannot contain uppercase header characters and must be treated as
|
||||
* malformed.
|
||||
*/
|
||||
private static ByteString checkLowercase(ByteString name) throws IOException {
|
||||
for (int i = 0, length = name.size(); i < length; i++) {
|
||||
byte c = name.getByte(i);
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
throw new IOException("PROTOCOL_ERROR response malformed: mixed case name: " + name.utf8());
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,770 +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 okhttp3.internal.framed;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
import okhttp3.Protocol;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSink;
|
||||
import okio.BufferedSource;
|
||||
import okio.ByteString;
|
||||
import okio.Source;
|
||||
import okio.Timeout;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.logging.Level.FINE;
|
||||
import static okhttp3.internal.framed.Http2.FrameLogger.formatHeader;
|
||||
import static okio.ByteString.EMPTY;
|
||||
|
||||
/**
|
||||
* Read and write HTTP/2 frames.
|
||||
*
|
||||
* <p>This implementation assumes we do not send an increased {@link Settings#getMaxFrameSize frame
|
||||
* size setting} to the peer. Hence, we expect all frames to have a max length of {@link
|
||||
* #INITIAL_MAX_FRAME_SIZE}. <p>http://tools.ietf.org/html/draft-ietf-httpbis-http2-17
|
||||
*/
|
||||
public final class Http2 implements Variant {
|
||||
private static final Logger logger = Logger.getLogger(FrameLogger.class.getName());
|
||||
|
||||
@Override public Protocol getProtocol() {
|
||||
return Protocol.HTTP_2;
|
||||
}
|
||||
|
||||
private static final ByteString CONNECTION_PREFACE
|
||||
= ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
|
||||
|
||||
/** The initial max frame size, applied independently writing to, or reading from the peer. */
|
||||
static final int INITIAL_MAX_FRAME_SIZE = 0x4000; // 16384
|
||||
|
||||
static final byte TYPE_DATA = 0x0;
|
||||
static final byte TYPE_HEADERS = 0x1;
|
||||
static final byte TYPE_PRIORITY = 0x2;
|
||||
static final byte TYPE_RST_STREAM = 0x3;
|
||||
static final byte TYPE_SETTINGS = 0x4;
|
||||
static final byte TYPE_PUSH_PROMISE = 0x5;
|
||||
static final byte TYPE_PING = 0x6;
|
||||
static final byte TYPE_GOAWAY = 0x7;
|
||||
static final byte TYPE_WINDOW_UPDATE = 0x8;
|
||||
static final byte TYPE_CONTINUATION = 0x9;
|
||||
|
||||
static final byte FLAG_NONE = 0x0;
|
||||
static final byte FLAG_ACK = 0x1; // Used for settings and ping.
|
||||
static final byte FLAG_END_STREAM = 0x1; // Used for headers and data.
|
||||
static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation.
|
||||
static final byte FLAG_END_PUSH_PROMISE = 0x4;
|
||||
static final byte FLAG_PADDED = 0x8; // Used for headers and data.
|
||||
static final byte FLAG_PRIORITY = 0x20; // Used for headers.
|
||||
static final byte FLAG_COMPRESSED = 0x20; // Used for data.
|
||||
|
||||
/**
|
||||
* Creates a frame reader with max header table size of 4096 and data frame compression disabled.
|
||||
*/
|
||||
@Override public FrameReader newReader(BufferedSource source, boolean client) {
|
||||
return new Reader(source, 4096, client);
|
||||
}
|
||||
|
||||
@Override public FrameWriter newWriter(BufferedSink sink, boolean client) {
|
||||
return new Writer(sink, client);
|
||||
}
|
||||
|
||||
static final class Reader implements FrameReader {
|
||||
private final BufferedSource source;
|
||||
private final ContinuationSource continuation;
|
||||
private final boolean client;
|
||||
|
||||
// Visible for testing.
|
||||
final Hpack.Reader hpackReader;
|
||||
|
||||
Reader(BufferedSource source, int headerTableSize, boolean client) {
|
||||
this.source = source;
|
||||
this.client = client;
|
||||
this.continuation = new ContinuationSource(this.source);
|
||||
this.hpackReader = new Hpack.Reader(headerTableSize, continuation);
|
||||
}
|
||||
|
||||
@Override public void readConnectionPreface() throws IOException {
|
||||
if (client) return; // Nothing to read; servers doesn't send a connection preface!
|
||||
ByteString connectionPreface = source.readByteString(CONNECTION_PREFACE.size());
|
||||
if (logger.isLoggable(FINE)) logger.fine(format("<< CONNECTION %s", connectionPreface.hex()));
|
||||
if (!CONNECTION_PREFACE.equals(connectionPreface)) {
|
||||
throw ioException("Expected a connection header but was %s", connectionPreface.utf8());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean nextFrame(Handler handler) throws IOException {
|
||||
try {
|
||||
source.require(9); // Frame header size
|
||||
} catch (IOException e) {
|
||||
return false; // This might be a normal socket close.
|
||||
}
|
||||
|
||||
/* 0 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | Length (24) |
|
||||
* +---------------+---------------+---------------+
|
||||
* | Type (8) | Flags (8) |
|
||||
* +-+-+-----------+---------------+-------------------------------+
|
||||
* |R| Stream Identifier (31) |
|
||||
* +=+=============================================================+
|
||||
* | Frame Payload (0...) ...
|
||||
* +---------------------------------------------------------------+
|
||||
*/
|
||||
int length = readMedium(source);
|
||||
if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) {
|
||||
throw ioException("FRAME_SIZE_ERROR: %s", length);
|
||||
}
|
||||
byte type = (byte) (source.readByte() & 0xff);
|
||||
byte flags = (byte) (source.readByte() & 0xff);
|
||||
int streamId = (source.readInt() & 0x7fffffff); // Ignore reserved bit.
|
||||
if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
|
||||
|
||||
switch (type) {
|
||||
case TYPE_DATA:
|
||||
readData(handler, length, flags, streamId);
|
||||
break;
|
||||
|
||||
case TYPE_HEADERS:
|
||||
readHeaders(handler, length, flags, streamId);
|
||||
break;
|
||||
|
||||
case TYPE_PRIORITY:
|
||||
readPriority(handler, length, flags, streamId);
|
||||
break;
|
||||
|
||||
case TYPE_RST_STREAM:
|
||||
readRstStream(handler, length, flags, streamId);
|
||||
break;
|
||||
|
||||
case TYPE_SETTINGS:
|
||||
readSettings(handler, length, flags, streamId);
|
||||
break;
|
||||
|
||||
case TYPE_PUSH_PROMISE:
|
||||
readPushPromise(handler, length, flags, streamId);
|
||||
break;
|
||||
|
||||
case TYPE_PING:
|
||||
readPing(handler, length, flags, streamId);
|
||||
break;
|
||||
|
||||
case TYPE_GOAWAY:
|
||||
readGoAway(handler, length, flags, streamId);
|
||||
break;
|
||||
|
||||
case TYPE_WINDOW_UPDATE:
|
||||
readWindowUpdate(handler, length, flags, streamId);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Implementations MUST discard frames that have unknown or unsupported types.
|
||||
source.skip(length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readHeaders(Handler handler, int length, byte flags, int streamId)
|
||||
throws IOException {
|
||||
if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0");
|
||||
|
||||
boolean endStream = (flags & FLAG_END_STREAM) != 0;
|
||||
|
||||
short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
|
||||
|
||||
if ((flags & FLAG_PRIORITY) != 0) {
|
||||
readPriority(handler, streamId);
|
||||
length -= 5; // account for above read.
|
||||
}
|
||||
|
||||
length = lengthWithoutPadding(length, flags, padding);
|
||||
|
||||
List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
|
||||
|
||||
handler.headers(false, endStream, streamId, -1, headerBlock, HeadersMode.HTTP_20_HEADERS);
|
||||
}
|
||||
|
||||
private List<Header> readHeaderBlock(int length, short padding, byte flags, int streamId)
|
||||
throws IOException {
|
||||
continuation.length = continuation.left = length;
|
||||
continuation.padding = padding;
|
||||
continuation.flags = flags;
|
||||
continuation.streamId = streamId;
|
||||
|
||||
// TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20.
|
||||
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-8.1.2.5
|
||||
hpackReader.readHeaders();
|
||||
return hpackReader.getAndResetHeaderList();
|
||||
}
|
||||
|
||||
private void readData(Handler handler, int length, byte flags, int streamId)
|
||||
throws IOException {
|
||||
// TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
|
||||
boolean inFinished = (flags & FLAG_END_STREAM) != 0;
|
||||
boolean gzipped = (flags & FLAG_COMPRESSED) != 0;
|
||||
if (gzipped) {
|
||||
throw ioException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA");
|
||||
}
|
||||
|
||||
short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
|
||||
length = lengthWithoutPadding(length, flags, padding);
|
||||
|
||||
handler.data(inFinished, streamId, source, length);
|
||||
source.skip(padding);
|
||||
}
|
||||
|
||||
private void readPriority(Handler handler, int length, byte flags, int streamId)
|
||||
throws IOException {
|
||||
if (length != 5) throw ioException("TYPE_PRIORITY length: %d != 5", length);
|
||||
if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
|
||||
readPriority(handler, streamId);
|
||||
}
|
||||
|
||||
private void readPriority(Handler handler, int streamId) throws IOException {
|
||||
int w1 = source.readInt();
|
||||
boolean exclusive = (w1 & 0x80000000) != 0;
|
||||
int streamDependency = (w1 & 0x7fffffff);
|
||||
int weight = (source.readByte() & 0xff) + 1;
|
||||
handler.priority(streamId, streamDependency, weight, exclusive);
|
||||
}
|
||||
|
||||
private void readRstStream(Handler handler, int length, byte flags, 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 = source.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 length, byte flags, int streamId)
|
||||
throws IOException {
|
||||
if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
|
||||
if ((flags & FLAG_ACK) != 0) {
|
||||
if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!");
|
||||
handler.ackSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
if (length % 6 != 0) throw ioException("TYPE_SETTINGS length %% 6 != 0: %s", length);
|
||||
Settings settings = new Settings();
|
||||
for (int i = 0; i < length; i += 6) {
|
||||
short id = source.readShort();
|
||||
int value = source.readInt();
|
||||
|
||||
switch (id) {
|
||||
case 1: // SETTINGS_HEADER_TABLE_SIZE
|
||||
break;
|
||||
case 2: // SETTINGS_ENABLE_PUSH
|
||||
if (value != 0 && value != 1) {
|
||||
throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1");
|
||||
}
|
||||
break;
|
||||
case 3: // SETTINGS_MAX_CONCURRENT_STREAMS
|
||||
id = 4; // Renumbered in draft 10.
|
||||
break;
|
||||
case 4: // SETTINGS_INITIAL_WINDOW_SIZE
|
||||
id = 7; // Renumbered in draft 10.
|
||||
if (value < 0) {
|
||||
throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1");
|
||||
}
|
||||
break;
|
||||
case 5: // SETTINGS_MAX_FRAME_SIZE
|
||||
if (value < INITIAL_MAX_FRAME_SIZE || value > 16777215) {
|
||||
throw ioException("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: %s", value);
|
||||
}
|
||||
break;
|
||||
case 6: // SETTINGS_MAX_HEADER_LIST_SIZE
|
||||
break; // Advisory only, so ignored.
|
||||
default:
|
||||
break; // Must ignore setting with unknown id.
|
||||
}
|
||||
settings.set(id, 0, value);
|
||||
}
|
||||
handler.settings(false, settings);
|
||||
if (settings.getHeaderTableSize() >= 0) {
|
||||
hpackReader.headerTableSizeSetting(settings.getHeaderTableSize());
|
||||
}
|
||||
}
|
||||
|
||||
private void readPushPromise(Handler handler, int length, byte flags, int streamId)
|
||||
throws IOException {
|
||||
if (streamId == 0) {
|
||||
throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0");
|
||||
}
|
||||
short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
|
||||
int promisedStreamId = source.readInt() & 0x7fffffff;
|
||||
length -= 4; // account for above read.
|
||||
length = lengthWithoutPadding(length, flags, padding);
|
||||
List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
|
||||
handler.pushPromise(streamId, promisedStreamId, headerBlock);
|
||||
}
|
||||
|
||||
private void readPing(Handler handler, int length, byte flags, 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 = source.readInt();
|
||||
int payload2 = source.readInt();
|
||||
boolean ack = (flags & FLAG_ACK) != 0;
|
||||
handler.ping(ack, payload1, payload2);
|
||||
}
|
||||
|
||||
private void readGoAway(Handler handler, int length, byte flags, int streamId)
|
||||
throws IOException {
|
||||
if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
|
||||
if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0");
|
||||
int lastStreamId = source.readInt();
|
||||
int errorCodeInt = source.readInt();
|
||||
int opaqueDataLength = length - 8;
|
||||
ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
|
||||
if (errorCode == null) {
|
||||
throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
|
||||
}
|
||||
ByteString debugData = EMPTY;
|
||||
if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection.
|
||||
debugData = source.readByteString(opaqueDataLength);
|
||||
}
|
||||
handler.goAway(lastStreamId, errorCode, debugData);
|
||||
}
|
||||
|
||||
private void readWindowUpdate(Handler handler, int length, byte flags, int streamId)
|
||||
throws IOException {
|
||||
if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length);
|
||||
long increment = (source.readInt() & 0x7fffffffL);
|
||||
if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
|
||||
handler.windowUpdate(streamId, increment);
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
source.close();
|
||||
}
|
||||
}
|
||||
|
||||
static final class Writer implements FrameWriter {
|
||||
private final BufferedSink sink;
|
||||
private final boolean client;
|
||||
private final Buffer hpackBuffer;
|
||||
private final Hpack.Writer hpackWriter;
|
||||
private int maxFrameSize;
|
||||
private boolean closed;
|
||||
|
||||
Writer(BufferedSink sink, boolean client) {
|
||||
this.sink = sink;
|
||||
this.client = client;
|
||||
this.hpackBuffer = new Buffer();
|
||||
this.hpackWriter = new Hpack.Writer(hpackBuffer);
|
||||
this.maxFrameSize = INITIAL_MAX_FRAME_SIZE;
|
||||
}
|
||||
|
||||
@Override public synchronized void flush() throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
@Override public synchronized void ackSettings(Settings peerSettings) throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
this.maxFrameSize = peerSettings.getMaxFrameSize(maxFrameSize);
|
||||
int length = 0;
|
||||
byte type = TYPE_SETTINGS;
|
||||
byte flags = FLAG_ACK;
|
||||
int streamId = 0;
|
||||
frameHeader(streamId, length, type, flags);
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
@Override public synchronized void connectionPreface() throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
if (!client) return; // Nothing to write; servers don't send connection headers!
|
||||
if (logger.isLoggable(FINE)) {
|
||||
logger.fine(format(">> CONNECTION %s", CONNECTION_PREFACE.hex()));
|
||||
}
|
||||
sink.write(CONNECTION_PREFACE.toByteArray());
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
@Override public synchronized void synStream(boolean outFinished, boolean inFinished,
|
||||
int streamId, int associatedStreamId, List<Header> headerBlock)
|
||||
throws IOException {
|
||||
if (inFinished) throw new UnsupportedOperationException();
|
||||
if (closed) throw new IOException("closed");
|
||||
headers(outFinished, streamId, headerBlock);
|
||||
}
|
||||
|
||||
@Override public synchronized void synReply(boolean outFinished, int streamId,
|
||||
List<Header> headerBlock) throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
headers(outFinished, streamId, headerBlock);
|
||||
}
|
||||
|
||||
@Override public synchronized void headers(int streamId, List<Header> headerBlock)
|
||||
throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
headers(false, streamId, headerBlock);
|
||||
}
|
||||
|
||||
@Override public synchronized void pushPromise(int streamId, int promisedStreamId,
|
||||
List<Header> requestHeaders) throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
hpackWriter.writeHeaders(requestHeaders);
|
||||
|
||||
long byteCount = hpackBuffer.size();
|
||||
int length = (int) Math.min(maxFrameSize - 4, byteCount);
|
||||
byte type = TYPE_PUSH_PROMISE;
|
||||
byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
|
||||
frameHeader(streamId, length + 4, type, flags);
|
||||
sink.writeInt(promisedStreamId & 0x7fffffff);
|
||||
sink.write(hpackBuffer, length);
|
||||
|
||||
if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
|
||||
}
|
||||
|
||||
void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
hpackWriter.writeHeaders(headerBlock);
|
||||
|
||||
long byteCount = hpackBuffer.size();
|
||||
int length = (int) Math.min(maxFrameSize, byteCount);
|
||||
byte type = TYPE_HEADERS;
|
||||
byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
|
||||
if (outFinished) flags |= FLAG_END_STREAM;
|
||||
frameHeader(streamId, length, type, flags);
|
||||
sink.write(hpackBuffer, length);
|
||||
|
||||
if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
|
||||
}
|
||||
|
||||
private void writeContinuationFrames(int streamId, long byteCount) throws IOException {
|
||||
while (byteCount > 0) {
|
||||
int length = (int) Math.min(maxFrameSize, byteCount);
|
||||
byteCount -= length;
|
||||
frameHeader(streamId, length, TYPE_CONTINUATION, byteCount == 0 ? FLAG_END_HEADERS : 0);
|
||||
sink.write(hpackBuffer, length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
|
||||
throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
if (errorCode.httpCode == -1) throw new IllegalArgumentException();
|
||||
|
||||
int length = 4;
|
||||
byte type = TYPE_RST_STREAM;
|
||||
byte flags = FLAG_NONE;
|
||||
frameHeader(streamId, length, type, flags);
|
||||
sink.writeInt(errorCode.httpCode);
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
@Override public int maxDataLength() {
|
||||
return maxFrameSize;
|
||||
}
|
||||
|
||||
@Override public synchronized void data(boolean outFinished, int streamId, Buffer source,
|
||||
int byteCount) throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
byte flags = FLAG_NONE;
|
||||
if (outFinished) flags |= FLAG_END_STREAM;
|
||||
dataFrame(streamId, flags, source, byteCount);
|
||||
}
|
||||
|
||||
void dataFrame(int streamId, byte flags, Buffer buffer, int byteCount) throws IOException {
|
||||
byte type = TYPE_DATA;
|
||||
frameHeader(streamId, byteCount, type, flags);
|
||||
if (byteCount > 0) {
|
||||
sink.write(buffer, byteCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public synchronized void settings(Settings settings) throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
int length = settings.size() * 6;
|
||||
byte type = TYPE_SETTINGS;
|
||||
byte flags = FLAG_NONE;
|
||||
int streamId = 0;
|
||||
frameHeader(streamId, length, type, flags);
|
||||
for (int i = 0; i < Settings.COUNT; i++) {
|
||||
if (!settings.isSet(i)) continue;
|
||||
int id = i;
|
||||
if (id == 4) {
|
||||
id = 3; // SETTINGS_MAX_CONCURRENT_STREAMS renumbered.
|
||||
} else if (id == 7) {
|
||||
id = 4; // SETTINGS_INITIAL_WINDOW_SIZE renumbered.
|
||||
}
|
||||
sink.writeShort(id);
|
||||
sink.writeInt(settings.get(i));
|
||||
}
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
@Override public synchronized void ping(boolean ack, int payload1, int payload2)
|
||||
throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
int length = 8;
|
||||
byte type = TYPE_PING;
|
||||
byte flags = ack ? FLAG_ACK : FLAG_NONE;
|
||||
int streamId = 0;
|
||||
frameHeader(streamId, length, type, flags);
|
||||
sink.writeInt(payload1);
|
||||
sink.writeInt(payload2);
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
@Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode,
|
||||
byte[] debugData) throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
if (errorCode.httpCode == -1) throw illegalArgument("errorCode.httpCode == -1");
|
||||
int length = 8 + debugData.length;
|
||||
byte type = TYPE_GOAWAY;
|
||||
byte flags = FLAG_NONE;
|
||||
int streamId = 0;
|
||||
frameHeader(streamId, length, type, flags);
|
||||
sink.writeInt(lastGoodStreamId);
|
||||
sink.writeInt(errorCode.httpCode);
|
||||
if (debugData.length > 0) {
|
||||
sink.write(debugData);
|
||||
}
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
@Override public synchronized void windowUpdate(int streamId, long windowSizeIncrement)
|
||||
throws IOException {
|
||||
if (closed) throw new IOException("closed");
|
||||
if (windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL) {
|
||||
throw illegalArgument("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: %s",
|
||||
windowSizeIncrement);
|
||||
}
|
||||
int length = 4;
|
||||
byte type = TYPE_WINDOW_UPDATE;
|
||||
byte flags = FLAG_NONE;
|
||||
frameHeader(streamId, length, type, flags);
|
||||
sink.writeInt((int) windowSizeIncrement);
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
@Override public synchronized void close() throws IOException {
|
||||
closed = true;
|
||||
sink.close();
|
||||
}
|
||||
|
||||
void frameHeader(int streamId, int length, byte type, byte flags) throws IOException {
|
||||
if (logger.isLoggable(FINE)) logger.fine(formatHeader(false, streamId, length, type, flags));
|
||||
if (length > maxFrameSize) {
|
||||
throw illegalArgument("FRAME_SIZE_ERROR length > %d: %d", maxFrameSize, length);
|
||||
}
|
||||
if ((streamId & 0x80000000) != 0) throw illegalArgument("reserved bit set: %s", streamId);
|
||||
writeMedium(sink, length);
|
||||
sink.writeByte(type & 0xff);
|
||||
sink.writeByte(flags & 0xff);
|
||||
sink.writeInt(streamId & 0x7fffffff);
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalArgumentException illegalArgument(String message, Object... args) {
|
||||
throw new IllegalArgumentException(format(message, args));
|
||||
}
|
||||
|
||||
private static IOException ioException(String message, Object... args) throws IOException {
|
||||
throw new IOException(format(message, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompression of the header block occurs above the framing layer. This class lazily reads
|
||||
* continuation frames as they are needed by {@link Hpack.Reader#readHeaders()}.
|
||||
*/
|
||||
static final class ContinuationSource implements Source {
|
||||
private final BufferedSource source;
|
||||
|
||||
int length;
|
||||
byte flags;
|
||||
int streamId;
|
||||
|
||||
int left;
|
||||
short padding;
|
||||
|
||||
public ContinuationSource(BufferedSource source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override public long read(Buffer sink, long byteCount) throws IOException {
|
||||
while (left == 0) {
|
||||
source.skip(padding);
|
||||
padding = 0;
|
||||
if ((flags & FLAG_END_HEADERS) != 0) return -1;
|
||||
readContinuationHeader();
|
||||
// TODO: test case for empty continuation header?
|
||||
}
|
||||
|
||||
long read = source.read(sink, Math.min(byteCount, left));
|
||||
if (read == -1) return -1;
|
||||
left -= read;
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override public Timeout timeout() {
|
||||
return source.timeout();
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
}
|
||||
|
||||
private void readContinuationHeader() throws IOException {
|
||||
int previousStreamId = streamId;
|
||||
|
||||
length = left = readMedium(source);
|
||||
byte type = (byte) (source.readByte() & 0xff);
|
||||
flags = (byte) (source.readByte() & 0xff);
|
||||
if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
|
||||
streamId = (source.readInt() & 0x7fffffff);
|
||||
if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type);
|
||||
if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed");
|
||||
}
|
||||
}
|
||||
|
||||
private static int lengthWithoutPadding(int length, byte flags, short padding)
|
||||
throws IOException {
|
||||
if ((flags & FLAG_PADDED) != 0) length--; // Account for reading the padding length.
|
||||
if (padding > length) {
|
||||
throw ioException("PROTOCOL_ERROR padding %s > remaining length %s", padding, length);
|
||||
}
|
||||
return (short) (length - padding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a human-readable representation of HTTP/2 frame headers.
|
||||
*
|
||||
* <p>The format is:
|
||||
*
|
||||
* <pre>
|
||||
* direction streamID length type flags
|
||||
* </pre>
|
||||
* Where direction is {@code <<} for inbound and {@code >>} for outbound.
|
||||
*
|
||||
* <p>For example, the following would indicate a HEAD request sent from the client.
|
||||
* <pre>
|
||||
* {@code
|
||||
* << 0x0000000f 12 HEADERS END_HEADERS|END_STREAM
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
static final class FrameLogger {
|
||||
|
||||
static String formatHeader(boolean inbound, int streamId, int length, byte type, byte flags) {
|
||||
String formattedType = type < TYPES.length ? TYPES[type] : format("0x%02x", type);
|
||||
String formattedFlags = formatFlags(type, flags);
|
||||
return format("%s 0x%08x %5d %-13s %s", inbound ? "<<" : ">>", streamId, length,
|
||||
formattedType, formattedFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up valid string representing flags from the table. Invalid combinations are represented
|
||||
* in binary.
|
||||
*/
|
||||
// Visible for testing.
|
||||
static String formatFlags(byte type, byte flags) {
|
||||
if (flags == 0) return "";
|
||||
switch (type) { // Special case types that have 0 or 1 flag.
|
||||
case TYPE_SETTINGS:
|
||||
case TYPE_PING:
|
||||
return flags == FLAG_ACK ? "ACK" : BINARY[flags];
|
||||
case TYPE_PRIORITY:
|
||||
case TYPE_RST_STREAM:
|
||||
case TYPE_GOAWAY:
|
||||
case TYPE_WINDOW_UPDATE:
|
||||
return BINARY[flags];
|
||||
}
|
||||
String result = flags < FLAGS.length ? FLAGS[flags] : BINARY[flags];
|
||||
// Special case types that have overlap flag values.
|
||||
if (type == TYPE_PUSH_PROMISE && (flags & FLAG_END_PUSH_PROMISE) != 0) {
|
||||
return result.replace("HEADERS", "PUSH_PROMISE"); // TODO: Avoid allocation.
|
||||
} else if (type == TYPE_DATA && (flags & FLAG_COMPRESSED) != 0) {
|
||||
return result.replace("PRIORITY", "COMPRESSED"); // TODO: Avoid allocation.
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Lookup table for valid frame types. */
|
||||
private static final String[] TYPES = new String[] {
|
||||
"DATA",
|
||||
"HEADERS",
|
||||
"PRIORITY",
|
||||
"RST_STREAM",
|
||||
"SETTINGS",
|
||||
"PUSH_PROMISE",
|
||||
"PING",
|
||||
"GOAWAY",
|
||||
"WINDOW_UPDATE",
|
||||
"CONTINUATION"
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup table for valid flags for DATA, HEADERS, CONTINUATION. Invalid combinations are
|
||||
* represented in binary.
|
||||
*/
|
||||
private static final String[] FLAGS = new String[0x40]; // Highest bit flag is 0x20.
|
||||
private static final String[] BINARY = new String[256];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < BINARY.length; i++) {
|
||||
BINARY[i] = format("%8s", Integer.toBinaryString(i)).replace(' ', '0');
|
||||
}
|
||||
|
||||
FLAGS[FLAG_NONE] = "";
|
||||
FLAGS[FLAG_END_STREAM] = "END_STREAM";
|
||||
|
||||
int[] prefixFlags = new int[] {FLAG_END_STREAM};
|
||||
|
||||
FLAGS[FLAG_PADDED] = "PADDED";
|
||||
for (int prefixFlag : prefixFlags) {
|
||||
FLAGS[prefixFlag | FLAG_PADDED] = FLAGS[prefixFlag] + "|PADDED";
|
||||
}
|
||||
|
||||
FLAGS[FLAG_END_HEADERS] = "END_HEADERS"; // Same as END_PUSH_PROMISE.
|
||||
FLAGS[FLAG_PRIORITY] = "PRIORITY"; // Same as FLAG_COMPRESSED.
|
||||
FLAGS[FLAG_END_HEADERS | FLAG_PRIORITY] = "END_HEADERS|PRIORITY"; // Only valid on HEADERS.
|
||||
int[] frameFlags =
|
||||
new int[] {FLAG_END_HEADERS, FLAG_PRIORITY, FLAG_END_HEADERS | FLAG_PRIORITY};
|
||||
|
||||
for (int frameFlag : frameFlags) {
|
||||
for (int prefixFlag : prefixFlags) {
|
||||
FLAGS[prefixFlag | frameFlag] = FLAGS[prefixFlag] + '|' + FLAGS[frameFlag];
|
||||
FLAGS[prefixFlag | frameFlag | FLAG_PADDED] =
|
||||
FLAGS[prefixFlag] + '|' + FLAGS[frameFlag] + "|PADDED";
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < FLAGS.length; i++) { // Fill in holes with binary representation.
|
||||
if (FLAGS[i] == null) FLAGS[i] = BINARY[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int readMedium(BufferedSource source) throws IOException {
|
||||
return (source.readByte() & 0xff) << 16
|
||||
| (source.readByte() & 0xff) << 8
|
||||
| (source.readByte() & 0xff);
|
||||
}
|
||||
|
||||
private static void writeMedium(BufferedSink sink, int i) throws IOException {
|
||||
sink.writeByte((i >>> 16) & 0xff);
|
||||
sink.writeByte((i >>> 8) & 0xff);
|
||||
sink.writeByte(i & 0xff);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user