Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use TreeMap to store headers #2113

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 19 additions & 60 deletions src/main/java/org/jsoup/helper/HttpConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import static org.jsoup.Connection.Method.HEAD;
import static org.jsoup.helper.DataUtil.UTF_8;
import static org.jsoup.internal.Normalizer.lowerCase;
import static org.jsoup.internal.SharedConstants.DefaultBufferSize;

/**
Expand Down Expand Up @@ -394,39 +394,30 @@ public Connection postDataCharset(String charset) {
}

@SuppressWarnings("unchecked")
private static abstract class Base<T extends Connection.Base<T>> implements Connection.Base<T> {
private static final URL UnsetUrl; // only used if you created a new Request()
static {
try {
UnsetUrl = new URL("http://undefined/");
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}

URL url = UnsetUrl;
Method method = Method.GET;
Map<String, List<String>> headers;
Map<String, String> cookies;
private abstract static class Base<T extends Connection.Base<T>> implements Connection.Base<T> {
protected URL url;
protected Method method = Method.GET;
protected final Map<String, List<String>> headers;
protected final Map<String, String> cookies;

private Base() {
headers = new LinkedHashMap<>();
headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
cookies = new LinkedHashMap<>();
}

private Base(Base<T> copy) {
this();
url = copy.url; // unmodifiable object
method = copy.method;
headers = new LinkedHashMap<>();
for (Map.Entry<String, List<String>> entry : copy.headers.entrySet()) {
headers.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
cookies = new LinkedHashMap<>(); cookies.putAll(copy.cookies); // just holds strings
cookies.putAll(copy.cookies); // just holds strings
}

@Override
public URL url() {
if (url == UnsetUrl)
if (url == null)
throw new IllegalArgumentException("URL not set. Make sure to call #url(...) before executing the request.");
return url;
}
Expand All @@ -453,8 +444,8 @@ public T method(Method method) {
@Override
public String header(String name) {
Validate.notNullParam(name, "name");
List<String> vals = getHeadersCaseInsensitive(name);
if (vals.size() > 0) {
List<String> vals = headers.get(name);
if (vals != null) {
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
return StringUtil.join(vals, ", ");
}
Expand All @@ -465,22 +456,17 @@ public String header(String name) {
@Override
public T addHeader(String name, @Nullable String value) {
Validate.notEmptyParam(name, "name");
value = value == null ? "" : value;

List<String> values = headers(name);
if (values.isEmpty()) {
values = new ArrayList<>();
headers.put(name, values);
}
values.add(value);
headers.computeIfAbsent(name, Functions.listFunction())
.add(value == null ? "" : value);

return (T) this;
}

@Override
public List<String> headers(String name) {
Validate.notEmptyParam(name, "name");
return getHeadersCaseInsensitive(name);
return headers.getOrDefault(name, Collections.emptyList());
}

@Override
Expand All @@ -494,7 +480,7 @@ public T header(String name, String value) {
@Override
public boolean hasHeader(String name) {
Validate.notEmptyParam(name, "name");
return !getHeadersCaseInsensitive(name).isEmpty();
return headers.containsKey(name);
}

/**
Expand All @@ -504,20 +490,14 @@ public boolean hasHeader(String name) {
public boolean hasHeaderWithValue(String name, String value) {
Validate.notEmpty(name);
Validate.notEmpty(value);
List<String> values = headers(name);
for (String candidate : values) {
if (value.equalsIgnoreCase(candidate))
return true;
}
return false;
return headers.getOrDefault(name, Collections.emptyList()).stream()
.anyMatch(value::equalsIgnoreCase);
}

@Override
public T removeHeader(String name) {
Validate.notEmptyParam(name, "name");
Map.Entry<String, List<String>> entry = scanHeaders(name); // remove is case-insensitive too
if (entry != null)
headers.remove(entry.getKey()); // ensures correct case
headers.remove(name); // remove is case-insensitive too
return (T) this;
}

Expand All @@ -538,26 +518,6 @@ public Map<String, List<String>> multiHeaders() {
return headers;
}

private List<String> getHeadersCaseInsensitive(String name) {
Validate.notNull(name);

for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
if (name.equalsIgnoreCase(entry.getKey()))
return entry.getValue();
}

return Collections.emptyList();
}

private Map.@Nullable Entry<String, List<String>> scanHeaders(String name) {
String lc = lowerCase(name);
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
if (lowerCase(entry.getKey()).equals(lc))
return entry;
}
return null;
}

@Override
public String cookie(String name) {
Validate.notEmptyParam(name, "name");
Expand Down Expand Up @@ -621,7 +581,6 @@ public static class Request extends HttpConnection.Base<Connection.Request> impl
maxBodySizeBytes = 1024 * 1024 * 2; // 2MB
followRedirects = true;
data = new ArrayList<>();
method = Method.GET;
addHeader("Accept-Encoding", "gzip");
addHeader(USER_AGENT, DEFAULT_UA);
parser = Parser.htmlParser();
Expand Down