/*
* Copyright 2012 Jason Miller
*
* 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 jj.http.client;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.QueryStringEncoder;
import io.netty.util.concurrent.Future;
/**
* @author jason
*
*/
@Singleton
public class HttpRequester {
private static final Map<String, Integer> SERVICES;
static {
Map<String, Integer> m = new HashMap<>();
m.put("http", 80);
m.put("https", 443);
SERVICES = Collections.unmodifiableMap(m);
}
private final HttpClient client;
@Inject
HttpRequester(final HttpClient client) {
this.client = client;
}
public Method requestTo(String uri) {
try {
return new Method(URI.create(uri));
} catch (Exception e) {
throw new AssertionError("ALWAYS VALIDATE THE URI BEFORE STARTING THE REQUEST");
}
}
public class Method {
private final URI uri;
private final Map<String, List<String>> params = new LinkedHashMap<>();
private Method(URI uri) {
this.uri = uri;
}
public Method param(CharSequence name, CharSequence value) {
params.computeIfAbsent(name.toString(), n -> {
return new ArrayList<>(1);
}).add(value.toString());
return this;
}
public Method params(Map<? extends CharSequence, ? extends CharSequence> params) {
params.forEach(this::param);
return this;
}
public Headers get() {
return method(HttpMethod.GET);
}
public Headers post() {
return method(HttpMethod.POST);
}
public Headers put() {
return method(HttpMethod.PUT);
}
public Headers delete() {
return method(HttpMethod.DELETE);
}
public Headers method(HttpMethod method) {
// validate the URI!
boolean secure = "https".equals(uri.getScheme());
int port = uri.getPort();
port = port == -1 ? (SERVICES.get(uri.getScheme())) : port;
return new Headers(secure, uri.getHost(), port, method, makeUri(uri));
}
private String makeUri(URI u) {
StringBuilder path = new StringBuilder(u.getPath());
for (Iterator<String> paramIter = params.keySet().iterator(); paramIter.hasNext();) {
String p = paramIter.next();
processParam(path, paramIter, p);
}
try {
URLEncoder.encode(path.toString(), UTF_8.name()).replace("+", "%20");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
QueryStringEncoder qse = new QueryStringEncoder(path.toString());
params.entrySet().forEach( entry -> {
entry.getValue().forEach(value -> {
qse.addParam(entry.getKey(), value);
});
});
return qse.toString();
}
private String popParam(String name) {
try {
return URLEncoder.encode(params.get(name).remove(0), UTF_8.name()).replace("+", "%20");
} catch (Exception e) {
throw new AssertionError(e);
}
}
private boolean processParam(StringBuilder sb, Iterator<String> paramIter, String p) {
int index = sb.indexOf(":" + p);
if (index > -1) {
sb.replace(index, index + p.length() + 1, popParam(p));
if (params.get(p).isEmpty()) paramIter.remove();
return true;
}
return false;
}
}
public class Headers {
private final boolean secure;
private final String host;
private final int port;
private final DefaultHttpRequest request;
private Headers(boolean secure, String host, int port, HttpMethod httpMethod, String uri) {
this.secure = secure;
this.host = host;
this.port = port;
request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, httpMethod, uri);
header(HttpHeaderNames.HOST, host);
header(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
}
public Headers header(CharSequence name, CharSequence value) {
request.headers().add(name, value);
return this;
}
private Channel ch(Future<?> future) {
return ((ChannelFuture) future).channel();
}
public void begin(HttpResponseListener listener) throws Exception {
client.connect(secure, host, port).addListener(future -> {
Channel ch = ch(future);
ch.writeAndFlush(request).addListener(f -> {
if (!f.isSuccess()) {
listener.requestErrored(f.cause());
} else {
ch.pipeline().addLast(listener.handler());
}
});
});
}
}
}