Skip to content

Commit 49baa4b

Browse files
unoexpertoslandelle
authored andcommitted
Implemented cookie jar and its use in http redirection (#1495), close #1235
Motivation: Existing implementation doesn't have proper support of cookie jar. It causes problem when requesting http URL that redirects to another location. Modifications: Implemented configurable CookieStore and default thread-safe implementation of it. Also modified redirection interceptor to use it. Result: Cookie store will be used for all http requests.
1 parent 730d088 commit 49baa4b

File tree

8 files changed

+738
-5
lines changed

8 files changed

+738
-5
lines changed

client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.asynchttpclient.channel.ChannelPool;
2222
import org.asynchttpclient.channel.KeepAliveStrategy;
23+
import org.asynchttpclient.cookie.CookieStore;
2324
import org.asynchttpclient.filter.IOExceptionFilter;
2425
import org.asynchttpclient.filter.RequestFilter;
2526
import org.asynchttpclient.filter.ResponseFilter;
@@ -182,6 +183,13 @@ public interface AsyncHttpClientConfig {
182183
*/
183184
List<IOExceptionFilter> getIoExceptionFilters();
184185

186+
/**
187+
* Return cookie store that is used to store and retrieve cookies
188+
*
189+
* @return {@link CookieStore} object
190+
*/
191+
CookieStore getCookieStore();
192+
185193
/**
186194
* Return the number of time the library will retry when an {@link java.io.IOException} is throw by the remote server
187195
*

client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818

1919
import static org.asynchttpclient.util.Assertions.assertNotNull;
2020

21+
import java.util.List;
2122
import java.util.concurrent.atomic.AtomicBoolean;
2223
import java.util.function.Predicate;
2324

25+
import io.netty.handler.codec.http.cookie.Cookie;
2426
import org.asynchttpclient.channel.ChannelPool;
2527
import org.asynchttpclient.filter.FilterContext;
2628
import org.asynchttpclient.filter.FilterException;
@@ -180,6 +182,22 @@ public BoundRequestBuilder prepareRequest(RequestBuilder requestBuilder) {
180182

181183
@Override
182184
public <T> ListenableFuture<T> executeRequest(Request request, AsyncHandler<T> handler) {
185+
if (config.getCookieStore() != null) {
186+
try {
187+
List<Cookie> cookies = config.getCookieStore().get(request.getUri());
188+
if (!cookies.isEmpty()) {
189+
RequestBuilder requestBuilder = new RequestBuilder(request);
190+
for (Cookie cookie : cookies)
191+
requestBuilder.addOrReplaceCookie(cookie);
192+
193+
request = requestBuilder.build();
194+
}
195+
} catch (Exception e) {
196+
handler.onThrowable(e);
197+
return new ListenableFuture.CompletedFailure<>("Failed to set cookies of request", e);
198+
}
199+
}
200+
183201
if (noRequestFilters) {
184202
return execute(request, handler);
185203
} else {

client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.asynchttpclient.channel.ChannelPool;
2727
import org.asynchttpclient.channel.DefaultKeepAliveStrategy;
2828
import org.asynchttpclient.channel.KeepAliveStrategy;
29+
import org.asynchttpclient.cookie.CookieStore;
30+
import org.asynchttpclient.cookie.ThreadSafeCookieStore;
2931
import org.asynchttpclient.filter.IOExceptionFilter;
3032
import org.asynchttpclient.filter.RequestFilter;
3133
import org.asynchttpclient.filter.ResponseFilter;
@@ -110,6 +112,9 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
110112
private final List<ResponseFilter> responseFilters;
111113
private final List<IOExceptionFilter> ioExceptionFilters;
112114

115+
// cookie store
116+
private final CookieStore cookieStore;
117+
113118
// internals
114119
private final String threadPoolName;
115120
private final int httpClientCodecMaxInitialLineLength;
@@ -186,6 +191,9 @@ private DefaultAsyncHttpClientConfig(//
186191
List<ResponseFilter> responseFilters,//
187192
List<IOExceptionFilter> ioExceptionFilters,//
188193

194+
// cookie store
195+
CookieStore cookieStore,
196+
189197
// tuning
190198
boolean tcpNoDelay,//
191199
boolean soReuseAddress,//
@@ -263,6 +271,9 @@ private DefaultAsyncHttpClientConfig(//
263271
this.responseFilters = responseFilters;
264272
this.ioExceptionFilters = ioExceptionFilters;
265273

274+
// cookie store
275+
this.cookieStore = cookieStore;
276+
266277
// tuning
267278
this.tcpNoDelay = tcpNoDelay;
268279
this.soReuseAddress = soReuseAddress;
@@ -502,6 +513,12 @@ public List<IOExceptionFilter> getIoExceptionFilters() {
502513
return ioExceptionFilters;
503514
}
504515

516+
// cookie store
517+
@Override
518+
public CookieStore getCookieStore() {
519+
return cookieStore;
520+
}
521+
505522
// tuning
506523
@Override
507524
public boolean isTcpNoDelay() {
@@ -676,6 +693,9 @@ public static class Builder {
676693
private final List<ResponseFilter> responseFilters = new LinkedList<>();
677694
private final List<IOExceptionFilter> ioExceptionFilters = new LinkedList<>();
678695

696+
// cookie store
697+
private CookieStore cookieStore = new ThreadSafeCookieStore();
698+
679699
// tuning
680700
private boolean tcpNoDelay = defaultTcpNoDelay();
681701
private boolean soReuseAddress = defaultSoReuseAddress();
@@ -1017,6 +1037,12 @@ public Builder removeIOExceptionFilter(IOExceptionFilter ioExceptionFilter) {
10171037
return this;
10181038
}
10191039

1040+
// cookie store
1041+
public Builder setCookieStore(CookieStore cookieStore) {
1042+
this.cookieStore = cookieStore;
1043+
return this;
1044+
}
1045+
10201046
// tuning
10211047
public Builder setTcpNoDelay(boolean tcpNoDelay) {
10221048
this.tcpNoDelay = tcpNoDelay;
@@ -1191,6 +1217,7 @@ public DefaultAsyncHttpClientConfig build() {
11911217
requestFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(requestFilters), //
11921218
responseFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(responseFilters),//
11931219
ioExceptionFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(ioExceptionFilters),//
1220+
cookieStore,
11941221
tcpNoDelay, //
11951222
soReuseAddress, //
11961223
soLinger, //
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (c) 2017 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
15+
package org.asynchttpclient.cookie;
16+
17+
import io.netty.handler.codec.http.cookie.Cookie;
18+
import org.asynchttpclient.uri.Uri;
19+
20+
import java.net.CookieManager;
21+
import java.util.List;
22+
import java.util.function.Predicate;
23+
24+
/**
25+
* This interface represents an abstract store for {@link Cookie} objects.
26+
* <p>
27+
* <p>{@link CookieManager} will call {@code CookieStore.add} to save cookies
28+
* for every incoming HTTP response, and call {@code CookieStore.get} to
29+
* retrieve cookie for every outgoing HTTP request. A CookieStore
30+
* is responsible for removing HttpCookie instances which have expired.
31+
*
32+
* @since 2.1
33+
*/
34+
public interface CookieStore {
35+
/**
36+
* Adds one {@link Cookie} to the store. This is called for every incoming HTTP response.
37+
* If the given cookie has already expired it will not be added, but existing values will still be removed.
38+
* <p>
39+
* <p>A cookie to store may or may not be associated with an URI. If it
40+
* is not associated with an URI, the cookie's domain and path attribute
41+
* will indicate where it comes from. If it is associated with an URI and
42+
* its domain and path attribute are not specified, given URI will indicate
43+
* where this cookie comes from.
44+
* <p>
45+
* <p>If a cookie corresponding to the given URI already exists,
46+
* then it is replaced with the new one.
47+
*
48+
* @param uri the {@link Uri uri} this cookie associated with. if {@code null}, this cookie will not be associated with an URI
49+
* @param cookie the {@link Cookie cookie} to be added
50+
*/
51+
void add(Uri uri, Cookie cookie);
52+
53+
/**
54+
* Retrieve cookies associated with given URI, or whose domain matches the given URI. Only cookies that
55+
* have not expired are returned. This is called for every outgoing HTTP request.
56+
*
57+
* @param uri the {@link Uri uri} associated with the cookies to be returned
58+
* @return an immutable list of Cookie, return empty list if no cookies match the given URI
59+
*/
60+
List<Cookie> get(Uri uri);
61+
62+
/**
63+
* Get all not-expired cookies in cookie store.
64+
*
65+
* @return an immutable list of http cookies;
66+
* return empty list if there's no http cookie in store
67+
*/
68+
List<Cookie> getAll();
69+
70+
/**
71+
* Remove a cookie from store.
72+
*
73+
* @param predicate that indicates what cookies to remove
74+
* @return {@code true} if this store contained the specified cookie
75+
* @throws NullPointerException if {@code cookie} is {@code null}
76+
*/
77+
boolean remove(Predicate<Cookie> predicate);
78+
79+
/**
80+
* Remove all cookies in this cookie store.
81+
*
82+
* @return true if any cookies were purged.
83+
*/
84+
boolean clear();
85+
}

0 commit comments

Comments
 (0)