Use jitpack libraries

This commit is contained in:
seven332 committed 2016-04-30 19:03:59 +08:00
1 parent d186a6ac3b
commit e3113083bc
136 files changed
+473 -21172

No files matched your search

-12
View File
@@ -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
View File
@@ -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;
@@ -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>
Submodule beerbelly deleted from 99020f63ac.
Submodule conaco deleted from 27ba4001e4.
-4
View File
@@ -1,4 +0,0 @@
/build
# Intellij
*.iml
-41
View File
@@ -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'
}
-17
View File
@@ -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);
}
}
-21
View File
@@ -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>
-183
View File
@@ -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;
}
-742
View File
@@ -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);
}
}
}
-74
View File
@@ -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);
}
}
}
-590
View File
@@ -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();
}
}
-49
View File
@@ -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;
}
-124
View File
@@ -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;
}
}
-303
View File
@@ -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;
}
}
-302
View File
@@ -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);
}
}
}
-260
View File
@@ -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);
}
}
};
}
}
-381
View File
@@ -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;
}
};
}
}
-99
View File
@@ -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