Kami akan menulis aplikasi web kecil, tanpa menggunakan kerangka kerja Web, perpustakaan eksternal, dan server aplikasi.
Tujuan artikel ini adalah untuk menunjukkan esensi umum dari apa yang terjadi di bawah kap layanan web, menggunakan Java sebagai contoh. Jadi ayo pergi. Kita seharusnya tidak menggunakan perpustakaan pihak ketiga, serta servlet. Karena itu, proyek tersebut akan dirakit oleh Maven, tetapi tanpa ketergantungan.
Apa yang terjadi ketika pengguna memasukkan alamat ip tertentu (atau dns yang berubah menjadi alamat ip) di bilah alamat browser? Permintaan dibuat ke ServerSocket dari host yang ditentukan, pada port yang ditentukan.
Kami mengatur di localhost kami, soket pada port bebas acak (misalnya 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; } }
Jangan lupa bahwa pendengar di port, sebagai objek, diinginkan untuk kita dalam satu salinan, oleh karena itu singleton (tidak harus periksa ulang, tetapi bisa jadi begitu).
Sekarang di host-e (localhost) kami di port 9001, ada pendengar yang menerima apa yang pengguna masukkan sebagai aliran byte.
Jika Anda mengurangi byte [] dari soket di DataInputStream dan mengonversinya menjadi string, Anda mendapatkan sesuatu seperti ini:
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
Permintaan Http standar dengan semua tajuk yang diperlukan.
Untuk parsing, kami akan membuat util-interface kecil dengan metode default, menurut saya itu cukup nyaman untuk tujuan seperti itu (selain itu, jika masih musim semi, maka kami mengurangi jumlah dependensi di kelas).
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) {
Utilitas ini dapat menguraikan jenis permintaan, url, dan daftar parameter untuk permintaan GET dan POST.
Dalam proses penguraian, kami membentuk model permintaan, dengan url target dan Peta dengan parameter permintaan.
Pengontrol untuk layanan kami adalah abstraksi kecil ke perpustakaan tempat kami dapat menambahkan buku (dalam implementasi ini, hanya ke Daftar), menghapus buku dan mengembalikan daftar semua buku.
1. Pengendali
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); } }
Kami juga memiliki pengontrol tunggal.
Kami mendaftarkan Pemetaan Permintaan. Berhenti, kami melakukannya tanpa kerangka kerja, apa Pemetaan Permintaan? Kita harus menulis anotasi ini sendiri.
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequestMapping { String path() default "/"; }
Dimungkinkan juga untuk menambahkan anotasi
Pengontrol di atas kelas, dan saat startup aplikasi, kumpulkan semua kelas yang ditandai dengan anotasi ini, dan metode mereka, dan tambahkan mereka ke Peta tertentu dengan url pemetaan. Tetapi dalam implementasi saat ini kami akan membatasi diri untuk satu pengontrol.
Sebelum pengontrol, kita akan memiliki PreProcessor tertentu, yang akan membentuk model permintaan yang dapat dimengerti oleh program dan memetakan ke metode pengontrol.
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. Model
Sebagai model kita akan memiliki Buku kelas
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 + '\''; } }
dan layanan
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) {
yang akan mengumpulkan koleksi buku, dan memasukkan data Model (beberapa Peta) yang diterima dari layanan.
3. Lihat
Sebagai Tampilan, kami akan membuat templat html, dan menempatkannya di direktori sumber / halaman terpisah, melanjutkan tingkat presentasi.
<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>
Kami menulis mesin templat kami sendiri, kelas harus dapat mengevaluasi respons yang diterima dari layanan, dan menghasilkan header http yang diperlukan (dalam kasus kami, OK atau PERMINTAAN BAD), ganti variabel yang diperlukan dalam dokumen HTML dengan nilai-nilai dari Model, dan akhirnya membuat HTML lengkap yang dapat dipahami oleh browser dan pengguna.
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 ""; } }
Sebagai ujian aplikasi untuk kinerja, kami menambahkan beberapa buku ke aplikasi kami:

Terima kasih telah membaca sampai akhir, artikel ini hanya untuk panduan, saya harap itu sedikit menarik dan sedikit bermanfaat.