Java tutorial
/* * Copyright (c) 2015 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper - initial API and implementation */ package org.eclipse.userstorage.tests.util; import org.eclipse.userstorage.internal.util.IOUtil; import org.eclipse.userstorage.internal.util.JSONUtil; import org.eclipse.userstorage.internal.util.StringUtil; import org.eclipse.userstorage.spi.Credentials; import org.eclipse.userstorage.tests.StorageTests; import org.eclipse.core.runtime.Path; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.UUID; /** * @author Eike Stepper */ public final class USSServer { public static final String BLOB_EXTENSION = ".blob"; public static final String ETAG_EXTENSION = ".etag"; private static final boolean DEBUG = Boolean.getBoolean("org.eclipse.userstorage.tests.server.debug"); private final USSHandler handler = new USSHandler(); private final Set<String> applicationTokens = new HashSet<String>(); private final Map<String, User> users = new HashMap<String, User>(); private final Map<String, Session> sessions = new HashMap<String, Session>(); private final File folder; private final int startPort; private int port; private Server server; public USSServer(int startPort, File folder) { this.startPort = startPort; this.folder = folder; } public int getPort() { return port; } public File getFolder() { return folder; } public Set<String> getApplicationTokens() { return applicationTokens; } public Map<String, User> getUsers() { return users; } public User addUser(Credentials credentials) { return addUser(credentials.getUsername(), credentials.getPassword()); } public User addUser(String username, String password) { User user = new User(username, password); users.put(user.getUsername(), user); return user; } public File getUserFile(User user, String applicationToken, String key, String extension) { return new File(getApplicationFolder(user, applicationToken), key + StringUtil.safe(extension)); } public File getApplicationFolder(User user, String applicationToken) { return new File(new File(folder, user.getUsername()), applicationToken); } public Map<String, Session> getSessions() { return sessions; } public int start() throws Exception { Exception exception = new Exception("No free port"); for (port = startPort; port < 65535; port++) { server = new Server(port); server.setHandler(handler); try { server.start(); return port; } catch (Exception ex) { exception = ex; try { server.stop(); } catch (Exception ignore) { //$FALL-THROUGH$ } finally { server = null; } } } throw exception; } public void stop() throws Exception { if (server != null) { server.stop(); server = null; } } public void join() throws InterruptedException { if (server != null) { server.join(); } } protected void login(HttpServletRequest request, HttpServletResponse response) throws IOException { Map<String, Object> requestObject = JSONUtil.parse(request.getInputStream(), null); String username = (String) requestObject.get("username"); String password = (String) requestObject.get("password"); User user = users.get(username); if (user == null || password == null || !password.equals(user.getPassword())) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } response.setStatus(HttpServletResponse.SC_OK); response.setContentType("application/json"); Session session = addSession(user); Cookie cookie = new Cookie("SESSION", session.getID()); cookie.setPath("/"); response.addCookie(cookie); Map<String, Object> responseObject = new LinkedHashMap<String, Object>(); responseObject.put("sessid", session.getID()); responseObject.put("token", session.getCSRFToken()); InputStream body = JSONUtil.build(responseObject); try { ServletOutputStream out = response.getOutputStream(); IOUtil.copy(body, out); out.flush(); } finally { IOUtil.closeSilent(body); } } protected void retrieveProperties(HttpServletRequest request, HttpServletResponse response, File applicationFolder) throws IOException { String applicationToken = applicationFolder.getName(); int pageSize = getIntParameter(request, "pageSize", 20); if (pageSize < 1 || pageSize > 100) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid page size"); return; } int page = getIntParameter(request, "page", 20); if (page < 1) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid page"); return; } boolean empty = true; StringBuilder builder = new StringBuilder(); builder.append('['); File[] files = applicationFolder.listFiles(); if (files != null) { int first = (page - 1) * pageSize + 1; System.out.println("##### " + first); int i = 0; for (File file : files) { String name = file.getName(); if (name.endsWith(ETAG_EXTENSION)) { if (++i >= first) { String key = name.substring(0, name.length() - ETAG_EXTENSION.length()); System.out.println("##### " + key); String etag = IOUtil.readUTF(file); if (empty) { empty = false; } else { builder.append(","); } builder.append("{\"application_token\":\""); builder.append(applicationToken); builder.append("\",\"key\":\""); builder.append(key); builder.append("\",\"etag\":\""); builder.append(etag); builder.append("\"}"); if (--pageSize == 0) { break; } } } } } builder.append(']'); System.out.println(builder); response.setStatus(HttpServletResponse.SC_OK); response.setContentType("application/json"); InputStream body = IOUtil.streamUTF(builder.toString()); try { ServletOutputStream out = response.getOutputStream(); IOUtil.copy(body, out); out.flush(); } finally { IOUtil.closeSilent(body); } } protected void retrieveBlob(HttpServletRequest request, HttpServletResponse response, File blobFile, File etagFile, boolean exists) throws IOException { if (!exists) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } String etag = IOUtil.readUTF(etagFile); String ifNoneMatch = getETag(request, "If-None-Match"); if (ifNoneMatch != null && ifNoneMatch.equals(etag)) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } response.setStatus(HttpServletResponse.SC_OK); response.setContentType("application/json"); response.setHeader("ETag", "\"" + etag + "\""); InputStream body = JSONUtil.build(Collections.singletonMap("value", new FileInputStream(blobFile))); try { ServletOutputStream out = response.getOutputStream(); IOUtil.copy(body, out); out.flush(); } finally { IOUtil.closeSilent(body); } } protected void updateBlob(HttpServletRequest request, HttpServletResponse response, File blobFile, File etagFile, boolean exists) throws IOException { String ifMatch = getETag(request, "If-Match"); if (exists) { String etag = IOUtil.readUTF(etagFile); if (StringUtil.isEmpty(ifMatch) || !ifMatch.equals(etag)) { response.setHeader("ETag", "\"" + etag + "\""); response.sendError(HttpServletResponse.SC_CONFLICT); return; } } String etag = UUID.randomUUID().toString(); IOUtil.mkdirs(blobFile.getParentFile()); FileOutputStream out = new FileOutputStream(blobFile); InputStream body = null; try { Map<String, Object> requestObject = JSONUtil.parse(request.getInputStream(), "value"); body = (InputStream) requestObject.get("value"); IOUtil.copy(body, out); } finally { IOUtil.closeSilent(body); IOUtil.close(out); } IOUtil.writeUTF(etagFile, etag); response.setStatus(exists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED); response.setHeader("ETag", "\"" + etag + "\""); } protected void deleteBlob(HttpServletRequest request, HttpServletResponse response, File blobFile, File etagFile, boolean exists) throws IOException { if (exists) { String etag = IOUtil.readUTF(etagFile); String ifMatch = getETag(request, "If-Match"); if (ifMatch != null && !ifMatch.equals(etag)) { response.sendError(HttpServletResponse.SC_CONFLICT); return; } } else { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } IOUtil.delete(blobFile); IOUtil.delete(etagFile); response.setStatus(HttpServletResponse.SC_NO_CONTENT); } private Session getSession(HttpServletRequest request) { String csrfToken = request.getHeader("X-CSRF-Token"); if (csrfToken != null) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if ("SESSION".equals(cookie.getName())) { String sessionID = cookie.getValue(); Session session = sessions.get(sessionID); if (session != null && session.getCSRFToken().equals(csrfToken)) { return session; } break; } } } } return null; } private Session addSession(User user) { Session session = new Session(user); sessions.put(session.getID(), session); return session; } private static String getETag(HttpServletRequest request, String headerName) { String eTag = request.getHeader(headerName); if (eTag != null) { // Remove the quotes. eTag = eTag.substring(1, eTag.length() - 1); } return eTag; } @SuppressWarnings("restriction") private static String getReasonPhrase(int status) { try { return org.apache.http.impl.EnglishReasonPhraseCatalog.INSTANCE.getReason(status, null); } catch (Throwable ex) { return ""; } } private static int getIntParameter(HttpServletRequest request, String name, int defaultValue) { String parameter = request.getParameter(name); if (parameter != null) { try { return Integer.parseInt(parameter); } catch (NumberFormatException ex) { //$FALL-THROUGH$ } } return defaultValue; } /** * @author Eike Stepper */ private class USSHandler extends AbstractHandler { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { try { baseRequest.setHandled(true); String userAgent = request.getHeader("User-Agent"); if (!org.eclipse.userstorage.internal.Session.USER_AGENT_ID.equals(userAgent)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid User-Agent"); return; } String method = request.getMethod(); String path = request.getPathInfo(); if (DEBUG) { StringBuilder builder = new StringBuilder(); builder.append(method); builder.append(" "); builder.append(path); builder.append('\n'); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); Enumeration<String> headers = request.getHeaders(headerName); while (headers.hasMoreElements()) { String header = headers.nextElement(); builder.append(" "); builder.append(headerName); builder.append(": "); builder.append(header); builder.append('\n'); } } System.out.print(builder); System.out.flush(); } if (path != null && method != null) { handle(method, path, request, response); if (DEBUG) { int status = response.getStatus(); String reasonPhrase = getReasonPhrase(status); StringBuilder builder = new StringBuilder(); builder.append(request.getProtocol()); builder.append(" "); builder.append(status); if (!StringUtil.isEmpty(reasonPhrase)) { builder.append(" "); builder.append(reasonPhrase); } builder.append('\n'); for (String headerName : response.getHeaderNames()) { for (String header : response.getHeaders(headerName)) { builder.append(" "); builder.append(headerName); builder.append(": "); builder.append(header); builder.append('\n'); } } System.out.println(builder); } } } catch (IOException ex) { ex.printStackTrace(); throw ex; } catch (RuntimeException ex) { ex.printStackTrace(); throw ex; } } private void handle(String method, String path, HttpServletRequest request, HttpServletResponse response) throws IOException { if (path.equals("/api/user/login") && HttpMethod.POST.is(method)) { login(request, response); return; } Session session = getSession(request); // if (session == null) // { // response.sendError(HttpServletResponse.SC_UNAUTHORIZED); // return; // } if (path.startsWith("/api/blob")) { // User user = session.getUser(); User user = new User("eclipse_test_123456789", "plaintext123456789"); Path segments = new Path(path); String applicationToken = segments.segment(2); if (!applicationTokens.contains(applicationToken)) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } if (segments.segmentCount() < 4) { if (HttpMethod.GET.is(method)) { File applicationFolder = getApplicationFolder(user, applicationToken); retrieveProperties(request, response, applicationFolder); return; } response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } String key = segments.segment(3); File blobFile = getUserFile(user, applicationToken, key, BLOB_EXTENSION); File etagFile = getUserFile(user, applicationToken, key, ETAG_EXTENSION); boolean exists = etagFile.exists(); if (HttpMethod.GET.is(method)) { retrieveBlob(request, response, blobFile, etagFile, exists); return; } if (HttpMethod.PUT.is(method)) { updateBlob(request, response, blobFile, etagFile, exists); return; } if (HttpMethod.DELETE.is(method)) { deleteBlob(request, response, blobFile, etagFile, exists); return; } return; } response.sendError(HttpServletResponse.SC_FORBIDDEN); } } /** * @author Eike Stepper */ public static final class User { private final String username; private final byte[] password; public User(String username, String password) { this.username = username; this.password = StringUtil.encrypt(password); } public String getUsername() { return username; } public String getPassword() { return StringUtil.decrypt(password); } @Override public String toString() { return username; } @Override public int hashCode() { return username.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } User other = (User) obj; if (username == null) { if (other.username != null) { return false; } } else if (!username.equals(other.username)) { return false; } return true; } } /** * @author Eike Stepper */ public static final class Session { private final String id; private final String csrfToken; private final User user; public Session(User user) { id = UUID.randomUUID().toString(); csrfToken = UUID.randomUUID().toString(); this.user = user; } public String getID() { return id; } public String getCSRFToken() { return csrfToken; } public User getUser() { return user; } @Override public int hashCode() { return id.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Session other = (Session) obj; if (id == null) { if (other.id != null) { return false; } } else if (!id.equals(other.id)) { return false; } return true; } @Override public String toString() { return id + " -> " + user; } } /** * @author Eike Stepper */ public static class NOOPLogger implements Logger { @Override public String getName() { return "noop"; } @Override public void warn(String msg, Object... args) { } @Override public void warn(Throwable thrown) { } @Override public void warn(String msg, Throwable thrown) { } @Override public void info(String msg, Object... args) { } @Override public void info(Throwable thrown) { } @Override public void info(String msg, Throwable thrown) { } @Override public boolean isDebugEnabled() { return false; } @Override public void setDebugEnabled(boolean enabled) { } @Override public void debug(String msg, Object... args) { } @Override public void debug(Throwable thrown) { } @Override public void debug(String msg, Throwable thrown) { } @Override public void debug(String msg, long value) { } @Override public Logger getLogger(String name) { return this; } @Override public void ignore(Throwable ignored) { } } public static void main(String[] args) throws Exception { Log.setLog(new NOOPLogger()); USSServer server = new USSServer(8080, new File(System.getProperty("java.io.tmpdir"), "uss-server")); server.addUser(FixedCredentialsProvider.DEFAULT_CREDENTIALS); Set<String> applicationTokens = server.getApplicationTokens(); applicationTokens.add(StorageTests.APPLICATION_TOKEN); applicationTokens.add("cNhDr0INs8T109P8h6E1r_GvU3I"); // Oomph System.out.println(server.getFolder()); System.out.println("Listening on port " + server.start()); server.join(); } }