All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rapidoid.http.impl.RespImpl Maven / Gradle / Ivy

There is a newer version: 5.5.5
Show newest version
/*-
 * #%L
 * rapidoid-http-fast
 * %%
 * Copyright (C) 2014 - 2018 Nikolche Mihajlovski and contributors
 * %%
 * 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.
 * #L%
 */

package org.rapidoid.http.impl;

import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.buffer.Buf;
import org.rapidoid.cls.Cls;
import org.rapidoid.collection.Coll;
import org.rapidoid.config.BasicConfig;
import org.rapidoid.config.Conf;
import org.rapidoid.ctx.Ctxs;
import org.rapidoid.ctx.UserInfo;
import org.rapidoid.http.HttpUtils;
import org.rapidoid.http.MediaType;
import org.rapidoid.http.Req;
import org.rapidoid.http.Resp;
import org.rapidoid.http.customize.Customization;
import org.rapidoid.http.customize.LoginProvider;
import org.rapidoid.http.customize.RolesProvider;
import org.rapidoid.io.IO;
import org.rapidoid.net.AsyncLogic;
import org.rapidoid.u.U;
import org.rapidoid.util.MscOpts;
import org.rapidoid.util.Tokens;
import org.rapidoid.web.Screen;
import org.rapidoid.web.ScreenBean;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Map;
import java.util.Set;


@Authors("Nikolche Mihajlovski")
@Since("5.0.x")
public class RespImpl extends RapidoidThing implements Resp {

	private final ReqImpl req;

	private volatile Object result = null;

	private volatile Object body = null;

	private volatile Object raw = null;

	private volatile int code = 200;

	private volatile MediaType contentType = HttpUtils.getDefaultContentType();

	private final Map headers = Coll.synchronizedMap();

	private final Map cookies = Coll.synchronizedMap();

	private final Map model = Coll.synchronizedMap();

	private volatile String redirect = null;

	private volatile String filename = null;

	private volatile File file = null;

	private volatile String view = null;

	private volatile boolean mvc = false;

	private volatile Screen screen;

	private volatile ChunkedResponse chunked;

	public RespImpl(ReqImpl req) {
		this.req = req;
	}

	@Override
	public synchronized Resp result(Object content) {
		ensureCanChange();
		this.result = content;
		return this;
	}

	@Override
	public synchronized Object result() {
		return this.result;
	}

	@Override
	public synchronized Resp body(byte[] body) {
		ensureCanChange();
		this.body = body;
		return this;
	}

	@Override
	public synchronized Resp body(ByteBuffer body) {
		ensureCanChange();
		this.body = body;
		return this;
	}

	@Override
	public synchronized Object body() {
		return this.body;
	}

	@Override
	public synchronized Resp raw(byte[] raw) {
		ensureCanChange();
		this.raw = raw;
		return this;
	}

	@Override
	public synchronized Resp raw(ByteBuffer raw) {
		ensureCanChange();
		this.raw = raw;
		return this;
	}

	@Override
	public synchronized Object raw() {
		return this.raw;
	}

	@Override
	public synchronized Resp code(int code) {
		ensureCanChange();
		this.code = code;
		return this;
	}

	@Override
	public synchronized int code() {
		return this.code;
	}

	@Override
	public Map headers() {
		return isReadOnly() ? Collections.unmodifiableMap(this.headers) : this.headers;
	}

	@Override
	public Resp header(String name, String value) {
		headers().put(name, value);
		return this;
	}

	@Override
	public Map cookies() {
		return isReadOnly() ? Collections.unmodifiableMap(this.cookies) : this.cookies;
	}

	@Override
	public Resp cookie(String name, String value, String... extras) {

		if (U.notEmpty(extras)) {
			value += "; " + U.join("; ", extras);
		}

		if (!cookieContainsPath(extras)) {
			value += "; path=" + HttpUtils.cookiePath();
		}

		cookies().put(name, value);

		return this;
	}

	private static boolean cookieContainsPath(String[] extras) {
		for (String extra : extras) {
			if (extra.toLowerCase().startsWith("path=")) {
				return true;
			}
		}

		return false;
	}

	@Override
	public Map session() {
		return request().session();
	}

	@Override
	public Resp session(String name, Serializable value) {
		session().put(name, value);
		return this;
	}

	@Override
	public Map token() {
		return request().token();
	}

	@Override
	public Resp token(String name, Serializable value) {
		token().put(name, value);
		return this;
	}

	@Override
	public Map model() {
		return isReadOnly() ? Collections.unmodifiableMap(this.model) : this.model;
	}

	@Override
	public Resp model(String name, Object value) {
		model().put(name, value);
		return this;
	}

	@Override
	public synchronized Resp contentType(MediaType contentType) {
		ensureCanChange();
		this.contentType = contentType;
		return this;
	}

	@Override
	public synchronized MediaType contentType() {
		return this.contentType;
	}

	@Override
	public synchronized Resp redirect(String redirect) {
		ensureCanChange();
		this.redirect = redirect;
		return this;
	}

	@Override
	public synchronized String redirect() {
		return this.redirect;
	}

	@Override
	public synchronized Resp filename(String filename) {
		ensureCanChange();
		this.filename = filename;
		return this;
	}

	@Override
	public synchronized String filename() {
		return this.filename;
	}

	@Override
	public synchronized Resp file(File file) {
		ensureCanChange();
		this.file = file;
		return this;
	}

	@Override
	public synchronized File file() {
		return this.file;
	}

	private void ensureCanChange() {
		U.must(!req.isDone(), "The request was already processed, so the response can't be changed now!");
		U.must(!req.isRendering(), "The response rendering has already started, so the response can't be changed now!");
	}

	private boolean isReadOnly() {
		return req.isRendering() || req.isDone();
	}

	@Override
	public Resp done() {
		req.done();
		return this;
	}

	@Override
	public Resp html(Object content) {
		return contentType(MediaType.HTML_UTF_8).result(content);
	}

	@Override
	public Resp plain(Object content) {
		return contentType(MediaType.PLAIN_TEXT_UTF_8).result(content);
	}

	@Override
	public Resp json(Object content) {
		return contentType(MediaType.JSON).result(content);
	}

	@Override
	public Resp binary(Object content) {
		return contentType(MediaType.BINARY).result(content);
	}

	@Override
	public synchronized String view() {
		return view != null ? view : HttpUtils.viewName(req);
	}

	@Override
	public Resp noView() {
		return view("");
	}

	boolean hasCustomView() {
		return view != null;
	}

	@Override
	public synchronized Resp view(String view) {
		if (view != null) {
			HttpUtils.validateViewName(view);
			this.mvc(true);
		}

		this.view = view;
		return this;
	}

	@Override
	public synchronized boolean mvc() {
		return mvc;
	}

	@Override
	public synchronized Resp mvc(boolean mvc) {

		if (mvc) {
			U.must(MscOpts.hasRapidoidGUI(), "The rapidoid-gui module must be included for the MVC feature!");
		}

		this.mvc = mvc;
		return this;
	}

	@Override
	public Req request() {
		return req;
	}

	@Override
	public boolean login(String username, String password) {

		LoginProvider loginProvider = Customization.of(req).loginProvider();
		U.must(loginProvider != null, "A login provider wasn't set!");

		RolesProvider rolesProvider = Customization.of(req).rolesProvider();
		U.must(rolesProvider != null, "A roles provider wasn't set!");

		req.tokenChanged.set(true);

		boolean success;

		try {
			success = loginProvider.login(req, username, password);

			if (success) {
				Set roles = rolesProvider.getRolesForUser(req, username);

				long ttl = Conf.TOKEN.entry("ttl").or(0);
				long expiresOn = ttl > 0 ? U.time() + ttl : Long.MAX_VALUE;

				UserInfo user = new UserInfo(username, roles, null);
				Ctxs.required().setUser(user);

				request().token().put(Tokens._USER, username);
				request().token().put(Tokens._EXPIRES, expiresOn);
			}

		} catch (Throwable e) {
			throw U.rte("Login error!", e);
		}

		return success;
	}

	@Override
	public void logout() {
		HttpUtils.clearUserData(request());
		HttpUtils.setResponseTokenCookie(this, "");
		req.tokenChanged.set(true);
	}

	@Override
	public Screen screen() {
		if (screen == null) {
			synchronized (this) {
				if (screen == null) {
					screen = createScreen();
				}
			}
		}

		return screen;
	}

	@Override
	public void resume(AsyncLogic asyncLogic) {
		req.channel().resume(req.connectionId(), req.handle(), asyncLogic);
	}

	private Screen createScreen() {
		Screen screen = MscOpts.hasRapidoidGUI() ? GUIUtil.newPage() : new ScreenBean();
		initScreen(screen);
		return screen;
	}

	private void initScreen(Screen screen) {
		BasicConfig zone = HttpUtils.zone(req);

		String brand = zone.entry("brand").str().getOrNull();
		String title = zone.entry("title").str().getOrNull();

		String siteName = req.host();
		if (U.isEmpty(siteName)
			|| siteName.equals("localhost") || siteName.startsWith("localhost:")
			|| siteName.equals("127.0.0.1") || siteName.startsWith("127.0.0.1:")) {
			siteName = "Rapidoid";
		}

		screen.brand(U.or(brand, siteName));
		screen.title(U.or(title, siteName));

		screen.home(zone.entry("home").str().or("/"));

		screen.search(zone.entry("search").bool().or(false));
		screen.navbar(zone.entry("navbar").bool().or(brand != null));
		screen.fluid(zone.entry("fluid").bool().or(false));

		String cdn = zone.entry("cdn").or("auto");
		if (!"auto".equalsIgnoreCase(cdn)) {
			screen.cdn(Cls.bool(cdn));
		}

		if (zone.has("menu")) {
			screen.menu(zone.sub("menu").toMap());
		}
	}

	@Override
	public OutputStream out() {
		if (chunked == null) {
			synchronized (this) {
				if (chunked == null) {
					checkStreamingPreconditions();

					// the chunked response object, which buffers the response data
					chunked = new ChunkedResponse(this);

					// set the header early, so it can be overwritten by the app, if necessary
					header("Transfer-Encoding", "chunked");
				}
			}
		}

		return chunked;
	}

	private void checkStreamingPreconditions() {
		U.must(result() == null, "The response result has already been set, so cannot write the response through OutputStream, too!");
		U.must(body() == null, "The response body has already been set, so cannot write the response through OutputStream, too!");
		U.must(raw() == null, "The raw response has already been set, so cannot write the response through OutputStream, too!");
	}

	void startChunkedOutputStream() {
		req.doRendering(code(), null);
	}

	@Override
	public String toString() {
		return "RespImpl{" +
			(result != null ? "result=" + result : "") +
			(body != null ? ", body=" + body : "") +
			(raw != null ? ", raw=" + raw : "") +
			", code=" + code +
			(contentType != null ? ", contentType=" + contentType : "") +
			", headers=" + headers +
			", cookies=" + cookies +
			", model=" + model +
			(redirect != null ? ", redirect='" + redirect + '\'' : "") +
			(filename != null ? ", filename='" + filename + '\'' : "") +
			(file != null ? ", file=" + file : "") +
			(view != null ? ", view='" + view + '\'' : "") +
			", mvc=" + mvc +
			'}';
	}

	@Override
	public Resp chunk(byte[] data) {
		OutputStream chnk = out();

		synchronized (chnk) {
			try {
				chnk.flush();
			} catch (IOException e) {
				throw U.rte(e);
			}

			chunk(data, 0, data.length);
		}

		return this;
	}

	public void chunk(final byte[] data, final int offset, final int length) {
		U.notNull(data, "data");

		resume(new AsyncLogic() {
			@Override
			public boolean resumeAsync() {
				Buf out = req.channel().output();

				out.append(Integer.toHexString(length));
				out.append("\r\n");
				out.append(data, offset, length);
				out.append("\r\n");

				req.channel().send();

				return false;
			}
		});
	}

	void terminatingChunk() {
		resume(new AsyncLogic() {
			@Override
			public boolean resumeAsync() {
				Buf out = req.channel().output();
				out.append("0\r\n\r\n");
				return true;
			}
		});
	}

	void finish() {
		if (chunked != null && !chunked.isClosed()) {
			IO.close(chunked, false);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy