Wir werden eine kleine Webanwendung schreiben, ohne Webframeworks, externe Bibliotheken und einen Anwendungsserver zu verwenden.
Der Zweck dieses Artikels ist es, am Beispiel von Java die allgemeine Essenz dessen zu zeigen, was unter der Haube eines Webdienstes geschieht. Also lass uns gehen. Wir sollten keine Bibliotheken von Drittanbietern sowie ein Servlet verwenden. Daher wird das Projekt von Maven zusammengestellt, jedoch ohne Abhängigkeiten.
Was passiert, wenn ein Benutzer eine bestimmte IP-Adresse (oder DNS, die sich in eine IP-Adresse verwandelt) in die Adressleiste des Browsers eingibt? An den ServerSocket des angegebenen Hosts am angegebenen Port wird eine Anforderung gesendet.
Wir organisieren auf unserem lokalen Host einen Socket an einem zufälligen freien Port (zum Beispiel 9001).
public class HttpRequestSocket { private static volatile Socket socket; private HttpRequestSocket() { } public static Socket getInstance() throws IOException { if (socket == null) { synchronized (HttpRequestSocket.class) { if (socket == null) { socket = new ServerSocket(9001).accept(); } } } return socket; } }
Vergessen Sie nicht, dass der Listener am Port als Objekt für uns in einer einzigen Kopie wünschenswert ist, daher Singleton (nicht unbedingt doppelt prüfen, aber es kann so sein).
Auf unserem Host-e (localhost) an Port 9001 gibt es jetzt einen Listener, der das, was der Benutzer eingibt, als Bytestrom empfängt.
Wenn Sie Byte [] vom Socket im DataInputStream subtrahieren und in eine Zeichenfolge konvertieren, erhalten Sie Folgendes:
GET /index HTTP/1.1 Host: localhost:9001 Connection: keep-alive Cache-Control: no-cache Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 Postman-Token: 838f4680-a363-731d-aa74-10ee46b9a87a Accept: */* Accept-Encoding: gzip, deflate, br Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Standard-HTTP-Anforderung mit allen erforderlichen Headern.
Zum Parsen erstellen wir eine kleine Util-Schnittstelle mit Standardmethoden. Meiner Meinung nach ist dies für solche Zwecke recht praktisch (außerdem reduzieren wir die Anzahl der Abhängigkeiten in der Klasse, wenn es noch Frühling ist).
public interface InputStringUtil { default String parseRequestMapping(final String inputData) { return inputData.split((" "))[1]; } default RequestType parseRequestType(final String source) { return valueOf(source.split(("/"))[0].trim()); } default Map<String, String> parseRequestParameter(final String source) { if (parseRequestType(source) == GET) { return parseGetRequestParameter(source); } else { return parsePostRequestParameter(source); } } @SuppressWarnings("unused") class ParameterParser { static Map<String, String> parseGetRequestParameter(final String source) { final Map<String, String> parameterMap = new HashMap<>(); if(source.contains("?")){ final String parameterBlock = source.substring(source.indexOf("?") + 1, source.indexOf("HTTP")).trim(); for (final String s : parameterBlock.split(Pattern.quote("&"))) { parameterMap.put(s.split(Pattern.quote("="))[0], s.split(Pattern.quote("="))[1]); } } return parameterMap; } static Map<String, String> parsePostRequestParameter(final String source) {
Dieses Dienstprogramm kann den Anforderungstyp, die URL und eine Liste von Parametern für GET- und POST-Anforderungen analysieren.
Beim Parsen bilden wir das Anforderungsmodell mit der Ziel-URL und Map mit den Anforderungsparametern.
Der Controller für unseren Service ist eine kleine Abstraktion zur Bibliothek, in der wir Bücher hinzufügen (in dieser Implementierung nur zur Liste), Bücher löschen und eine Liste aller Bücher zurückgeben können.
1. Controller
public class BookController { private static volatile BookController bookController; private BookController() { } public static BookController getInstance() { if (bookController == null) { synchronized (BookController.class) { if (bookController == null) { bookController = new BookController(); } } } return bookController; } @RequestMapping(path = "/index") @SuppressWarnings("unused") public void index(final Map<String, String> paramMap) { final Map<String, List<DomainBook>> map = new HashMap<>(); map.put("book", DefaultBookService.getInstance().getCollection()); HtmlMarker.getInstance().makeTemplate("index", map); } @RequestMapping(path = "/add") @SuppressWarnings("unused") public void addBook(final Map<String, String> paramMap) { DefaultBookService.getInstance().addBook(paramMap); final Map<String, List<DomainBook>> map = new HashMap<>(); map.put("book", DefaultBookService.getInstance().getCollection()); HtmlMarker.getInstance().makeTemplate("index", map); } }
Wir haben auch einen Singleton-Controller.
Wir registrieren RequestMapping. Hör auf, wir machen es ohne Framework, was RequestMapping? Wir müssen diese Anmerkung selbst schreiben.
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequestMapping { String path() default "/"; }
Es war auch möglich, die
Controller- Annotation über die Klasse hinzuzufügen und beim Start der Anwendung alle mit dieser Annotation markierten Klassen und ihre Methoden zu sammeln und sie aus den URL-Zuordnungen zu einer bestimmten Map hinzuzufügen. In der aktuellen Implementierung beschränken wir uns jedoch auf einen Controller.
Vor dem Controller haben wir einen bestimmten PreProcessor, der das für das Programm verständliche Anforderungsmodell bildet und den Controller-Methoden zuordnet.
public class HttpRequestPreProcessor implements InputStringUtil { private final byte[] BYTE_BUFFER = new byte[1024]; public void doRequest() { try { while (true) { System.out.println("Socket open"); final Socket socket = HttpRequestSocket.getInstance(); final DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream())); final String inputUrl = new String(BYTE_BUFFER, 0, in.read(BYTE_BUFFER)); processRequest(inputUrl); System.out.println("send request " + inputUrl); } } catch (final IOException e) { e.printStackTrace(); } } private void processRequest(final String inputData) { final String urlMapping = parseRequestMapping(inputData); final Map<String, String> paramMap = parseRequestParameter(inputData); final Method[] methods = BookController.getInstance().getClass().getMethods(); for (final Method method : methods) { if (method.isAnnotationPresent(RequestMapping.class) && urlMapping.contains(method.getAnnotation(RequestMapping.class).path())) { try { method.invoke(BookController.getInstance(), paramMap); return; } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } } HtmlMarker.getInstance().makeTemplate("error", emptyMap()); }
2. Modell
Als Modell werden wir ein Klassenbuch haben
public class DomainBook { private String id; private String author; private String title; public DomainBook(String id, String author, String title) { this.id = id; this.author = author; this.title = title; } public String getId() { return id; } public String getAuthor() { return author; } public String getTitle() { return title; } @Override public String toString() { return "id=" + id + " author='" + author + '\'' + " title='" + title + '\''; } }
und Service
public class DefaultBookService implements BookService { private static volatile BookService bookService; private List<DomainBook> bookList = new ArrayList<>(); private DefaultBookService() { } public static BookService getInstance() { if (bookService == null) { synchronized (DefaultBookService.class) { if (bookService == null) { bookService = new DefaultBookService(); } } } return bookService; } @Override public List<DomainBook> getCollection() { System.out.println("get collection " + bookList); return bookList; } @Override public void addBook(Map<String, String> paramMap) { final DomainBook domainBook = new DomainBook(paramMap.get("id"), paramMap.get("author"), paramMap.get("title")); bookList.add(domainBook); System.out.println("add book " + domainBook); } @Override public void deleteBookById(long id) {
Dadurch wird eine Sammlung von Büchern gesammelt und die vom Dienst erhaltenen Modelldaten (einige Karten) eingegeben.
3. Anzeigen
Als Ansicht erstellen wir eine HTML-Vorlage und platzieren sie in einem separaten Ressourcen- / Seitenverzeichnis, um die Präsentationsebene zu verbessern.
<html> <head> <title>Example</title> </head> <br> <table> <td>${book.id}</td><td>${book.author}</td><td>${book.title}</td> </table> </br> </br> </br> <form method="get" action="/add"> <p>Number<input type="text" name="id"></p> <p>Author<input type="text" name="author"></p> <p>Title<input type="text" name="title"></p> <p><input type="submit" value="Send"></p> </form> </body> </html>
Wir schreiben unsere eigene Template-Engine. Die Klasse sollte in der Lage sein, die vom Service empfangene Antwort auszuwerten und den erforderlichen http-Header (in unserem Fall OK oder BAD REQUEST) zu generieren, die erforderlichen Variablen im HTML-Dokument durch die Werte aus dem Modell zu ersetzen und schließlich den vollständigen HTML-Code zu rendern, den der Browser und der Benutzer verstehen können.
public class HtmlMarker { private static volatile HtmlMarker htmlMarker; private HtmlMarker() { } public static HtmlMarker getInstance() { if (htmlMarker == null) { synchronized (HtmlMarker.class) { if (htmlMarker == null) { htmlMarker = new HtmlMarker(); } } } return htmlMarker; } public void makeTemplate(final String fileName, Map<String, List<DomainBook>> param) { try { final BufferedWriter bufferedWriter = new BufferedWriter( new OutputStreamWriter( new BufferedOutputStream(HttpRequestSocket.getInstance().getOutputStream()), StandardCharsets.UTF_8)); if (fileName.equals("error")) { bufferedWriter.write(ERROR + ERROR_MESSAGE.length() + OUTPUT_END_OF_HEADERS + readFile(fileName, param)); bufferedWriter.flush(); } else { bufferedWriter.write(SUCCESS + readFile(fileName, param).length() + OUTPUT_END_OF_HEADERS + readFile(fileName, param)); bufferedWriter.flush(); } } catch (IOException e) { e.printStackTrace(); } } private String readFile(final String fileName, Map<String, List<DomainBook>> param) { final StringBuilder builder = new StringBuilder(); final String path = "src\\resources\\pages\\" + fileName + ".html"; try (BufferedReader br = Files.newBufferedReader(Paths.get(path))) { String line; while ((line = br.readLine()) != null) { if (line.contains("${")) { final String key = line.substring(line.indexOf("{") + 1, line.indexOf("}")); final String keyPrefix = key.split(Pattern.quote("."))[0]; for (final DomainBook domainBook : param.get(keyPrefix)) { builder.append("<tr>"); builder.append( line.replace("${book.id}", domainBook.getId()) .replace("${book.author}", domainBook.getAuthor()) .replace("${book.title}", domainBook.getTitle()) ).append("</tr>"); } if(param.get(keyPrefix).isEmpty()){ builder.append(line.replace("${book.id}</td><td>${book.author}</td><td>${book.title}", "<p>library is EMPTY</p>")); } continue; } builder.append(line).append("\n"); } return builder.toString(); } catch (IOException e) { e.printStackTrace(); } return ""; } }
Als Test für die Leistungsanwendung fügen wir unserer Anwendung einige Bücher hinzu:

Vielen Dank für das Lesen bis zum Ende, der Artikel dient nur zur Orientierung, ich hoffe, dass er ein wenig interessant und ein wenig nützlich war.