/**
* 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 org.jooby.internal.undertow;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import org.jooby.Cookie;
import org.jooby.MediaType;
import org.jooby.Sse;
import org.jooby.spi.NativePushPromise;
import org.jooby.spi.NativeRequest;
import org.jooby.spi.NativeUpload;
import org.jooby.spi.NativeWebSocket;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.typesafe.config.Config;
import io.undertow.server.BlockingHttpExchange;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.form.FormData;
import io.undertow.server.handlers.form.FormData.FormValue;
import io.undertow.server.handlers.form.FormEncodedDataDefinition;
import io.undertow.server.handlers.form.MultiPartParserDefinition;
import io.undertow.util.AttachmentKey;
import io.undertow.util.HeaderValues;
import io.undertow.util.HttpString;
public class UndertowRequest implements NativeRequest {
public static final AttachmentKey<NativeWebSocket> SOCKET = AttachmentKey
.create(NativeWebSocket.class);
private static final FormData NO_FORM = new FormData(0);
private HttpServerExchange exchange;
private Config conf;
private FormData form;
private String path;
private Supplier<BlockingHttpExchange> blocking;
public UndertowRequest(final HttpServerExchange exchange, final Config conf) throws IOException {
this.exchange = exchange;
this.blocking = Suppliers.memoize(() -> this.exchange.startBlocking());
this.conf = conf;
this.path = URLDecoder.decode(exchange.getRequestPath(), "UTF-8");
}
@Override
public Optional<String> queryString() {
String q = exchange.getQueryString();
return q.length() == 0 ? Optional.empty() : Optional.of(q);
}
@Override
public String method() {
return exchange.getRequestMethod().toString();
}
@Override
public String path() {
return path;
}
@Override
public String rawPath() {
return exchange.getRequestURI();
}
@Override
public List<String> paramNames() {
ImmutableList.Builder<String> builder = ImmutableList.<String> builder();
builder.addAll(exchange.getQueryParameters().keySet());
FormData formdata = parseForm();
formdata.forEach(v -> {
// excludes upload from param names.
if (!formdata.getFirst(v).isFile()) {
builder.add(v);
}
});
return builder.build();
}
@Override
public List<String> params(final String name) {
Builder<String> builder = ImmutableList.builder();
// query params
Deque<String> query = exchange.getQueryParameters().get(name);
if (query != null) {
query.stream().forEach(builder::add);
}
// form params
Optional.ofNullable(parseForm().get(name)).ifPresent(values -> {
values.stream().forEach(value -> {
if (!value.isFile()) {
builder.add(value.getValue());
}
});
});
return builder.build();
}
@Override
public Optional<String> header(final String name) {
return Optional.ofNullable(exchange.getRequestHeaders().getFirst(name));
}
@Override
public List<String> headers(final String name) {
HeaderValues values = exchange.getRequestHeaders().get(name);
return values == null ? Collections.emptyList() : values;
}
@Override
public List<String> headerNames() {
return exchange.getRequestHeaders().getHeaderNames()
.stream()
.map(HttpString::toString)
.collect(Collectors.toList());
}
@Override
public List<Cookie> cookies() {
return exchange.getRequestCookies().values().stream()
.map(UndertowRequest::cookie)
.collect(Collectors.toList());
}
@Override
public List<NativeUpload> files(final String name) {
Builder<NativeUpload> builder = ImmutableList.builder();
Deque<FormValue> values = parseForm().get(name);
if (values != null) {
values.forEach(value -> {
if (value.isFile()) {
builder.add(new UndertowUpload(value));
}
});
}
return builder.build();
}
@Override
public InputStream in() {
blocking.get();
return exchange.getInputStream();
}
@Override
public String ip() {
return Optional.ofNullable(exchange.getSourceAddress())
.map(src -> Optional.ofNullable(src.getAddress())
.map(InetAddress::getHostAddress)
.orElse(""))
.orElse("");
}
@Override
public String protocol() {
return exchange.getProtocol().toString();
}
@Override
public boolean secure() {
return exchange.getRequestScheme().equalsIgnoreCase("https");
}
@Override
@SuppressWarnings("unchecked")
public <T> T upgrade(final Class<T> type) throws Exception {
if (type == NativeWebSocket.class) {
UndertowWebSocket ws = new UndertowWebSocket(conf);
exchange.putAttachment(SOCKET, ws);
return (T) ws;
}
if (type == Sse.class) {
return (T) new UndertowSse(exchange);
}
if (type == NativePushPromise.class) {
return (T) new UndertowPush(exchange);
}
throw new UnsupportedOperationException("Not Supported: " + type);
}
@Override
public void startAsync(final Executor executor, final Runnable runnable) {
exchange.dispatch(executor, runnable);
}
private FormData parseForm() {
if (form == null) {
form = NO_FORM;
try {
String tmpdir = conf.getString("application.tmpdir");
String charset = conf.getString("application.charset");
String value = exchange.getRequestHeaders().getFirst("Content-Type");
if (value != null) {
MediaType type = MediaType.valueOf(value);
if (MediaType.form.name().equals(type.name())) {
blocking.get();
form = new FormEncodedDataDefinition()
.setDefaultEncoding(charset)
.create(exchange)
.parseBlocking();
} else if (MediaType.multipart.name().equals(type.name())) {
blocking.get();
form = new MultiPartParserDefinition()
.setTempFileLocation(new File(tmpdir).toPath())
.setDefaultEncoding(charset)
.create(exchange)
.parseBlocking();
}
}
} catch (IOException x) {
}
}
return form;
}
private static Cookie cookie(final io.undertow.server.handlers.Cookie c) {
Cookie.Definition cookie = new Cookie.Definition(c.getName(), c.getValue());
Optional.ofNullable(c.getComment()).ifPresent(cookie::comment);
Optional.ofNullable(c.getDomain()).ifPresent(cookie::domain);
Optional.ofNullable(c.getPath()).ifPresent(cookie::path);
return cookie.toCookie();
}
}