लेख का अनुवाद विशेष रूप से पाठ्यक्रम के छात्रों के लिए तैयार किया गया था। "जावा डेवलपर । "
जावा पारिस्थितिकी तंत्र में कई चौखटे और पुस्तकालय हैं। हालाँकि जावास्क्रिप्ट में उतना नहीं है, लेकिन वे इतनी जल्दी समाप्त नहीं होते हैं। हालांकि, इससे मुझे लगा कि हम पहले ही भूल गए हैं कि बिना चौखटे के आवेदन कैसे लिखें।
आप कह सकते हैं कि
वसंत मानक है और पहिया को क्यों मजबूत करना है? और
स्पार्क एक अच्छा, सुविधाजनक REST फ्रेमवर्क है। या
लाइट-रेस्ट -4jis । और मैं कहूंगा कि आप निश्चित रूप से सही हैं।
लेकिन फ्रेमवर्क के साथ-साथ, समाप्त कार्यक्षमता के अलावा, आपको बहुत अधिक जादू मिलता है, सीखने के साथ कठिनाइयाँ, अतिरिक्त कार्य जो आप सबसे अधिक संभावना का उपयोग नहीं करेंगे, साथ ही बग भी। और आपकी सेवा में जितना अधिक तृतीय-पक्ष कोड होगा, उतनी अधिक संभावना होगी कि आपके पास त्रुटियां होंगी।
खुला स्रोत समुदाय बहुत सक्रिय है, और इसकी उच्च संभावना है कि ढांचे में त्रुटियां जल्दी से ठीक हो जाएंगी। लेकिन फिर भी, मैं आपसे यह सोचने का आग्रह करूंगा कि क्या आपको वास्तव में एक रूपरेखा की आवश्यकता है। यदि आपके पास एक छोटी सेवा या कंसोल एप्लिकेशन है, तो आप इसके बिना कर सकते हैं।
शुद्ध जावा कोड का उपयोग करके आप क्या हासिल कर सकते हैं (या खो सकते हैं)? इसके बारे में सोचो:
- आपका कोड ज्यादा साफ-सुथरा और अधिक समझने योग्य हो सकता है (या हो सकता है कि अगर आप एक खराब प्रोग्रामर हैं तो पूरी तरह गड़बड़ हैं)
- आपके पास अपने कोड पर अधिक नियंत्रण होगा, आप फ्रेमवर्क द्वारा सीमित नहीं होंगे (हालाँकि आपको कार्यक्षमता के लिए अपना अधिक कोड लिखना होगा जो फ्रेमवर्क बॉक्स से बाहर प्रदान करता है)
- आपके एप्लिकेशन को तैनात किया जाएगा और बहुत तेज़ी से लॉन्च किया जाएगा, क्योंकि फ़्रेमवर्क को दर्जनों कक्षाओं को शुरू करने की आवश्यकता नहीं है (या यदि आप कुछ मिलाते हैं तो यह बिल्कुल भी शुरू नहीं होगा, उदाहरण के लिए, मल्टीथ्रेडिंग में)
- यदि आप डॉकर में एप्लिकेशन को तैनात करते हैं, तो आपकी छवियां बहुत छोटी होंगी, क्योंकि आपका जार भी छोटा होगा
मैंने थोड़ा प्रयोग किया और फ्रेमवर्क के बिना REST API विकसित करने का प्रयास किया। शायद यह ज्ञान सीखने और ताज़ा करने के मामले में दिलचस्प होगा।
जब मैंने इस कोड को लिखना शुरू किया, तो मैं अक्सर उन स्थितियों में आया जब कोई कार्यक्षमता नहीं थी कि स्प्रिंग बॉक्स से बाहर हो। इन क्षणों में, वसंत लेने के बजाय, आपको अपने आप को पुनर्विचार और सब कुछ विकसित करना पड़ा।
मुझे एहसास हुआ कि वास्तविक व्यावसायिक समस्याओं को हल करने के लिए, मैं फिर भी पहिया को सुदृढ़ करने के बजाय स्प्रिंग का उपयोग करना पसंद करूंगा। हालांकि, मुझे लगता है कि यह अभ्यास काफी दिलचस्प अनुभव था।
शुरुआत हो रही है
मैं प्रत्येक चरण का वर्णन करूंगा, लेकिन मैं हमेशा पूर्ण स्रोत कोड नहीं दूंगा। आप
git रिपॉजिटरी की अलग-अलग शाखाओं में पूरा कोड देख सकते हैं।
पहले निम्नलिखित
pom.xml
साथ एक नया मावेन प्रोजेक्ट बनाएं।
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.consulner.httpserver</groupId> <artifactId>pure-java-rest-api</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>11</java.version> <maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.target>${java.version}</maven.compiler.target> </properties> <dependencies></dependencies> </project>
निर्भरता के रूप में
java.xml.bind
जोड़ें क्योंकि इसे JDK 11 (
JEP-320 ) में हटा दिया गया था।
<dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.4.0-b180608.0325</version> </dependency>
और
जैक्सन क्रमांकन के लिए
जैक्सन <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.7</version> </dependency>
POJO वर्गों को सरल बनाने के लिए, हम लोम्बोक का उपयोग करेंगे:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.0</version> <scope>provided</scope> </dependency>
और कार्यात्मक प्रोग्रामिंग टूल के लिए
vavr <dependency> <groupId>io.vavr</groupId> <artifactId>vavr</artifactId> <version>0.9.2</version> </dependency>
हम मुख्य खाली वर्ग
Application
भी बनाते हैं।
चरण -1 शाखा में स्रोत कोड।
पहला समापन बिंदु
हमारा वेब एप्लिकेशन वर्ग
com.sun.net.httpserver.HttpServer
पर आधारित होगा। और सबसे सरल समापन बिंदु
/api/hello
इस तरह दिखाई दे सकता है:
package com.consulner.api; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpServer; class Application { public static void main(String[] args) throws IOException { int serverPort = 8000; HttpServer server = HttpServer.create(new InetSocketAddress(serverPort), 0); server.createContext("/api/hello", (exchange -> { String respText = "Hello!"; exchange.sendResponseHeaders(200, respText.getBytes().length); OutputStream output = exchange.getResponseBody(); output.write(respText.getBytes()); output.flush(); exchange.close(); })); server.setExecutor(null);
वेब सर्वर पोर्ट 8000 पर चलता है और एक समापन बिंदु प्रदान करता है जो बस हैलो लौटाता है! .. यह जाँच की जा सकती है, उदाहरण के लिए, कर्ल का उपयोग करके:
curl localhost:8000/api/hello
चरण -2 शाखा में स्रोत कोड।
विभिन्न HTTP तरीकों के लिए समर्थन
हमारा पहला समापन बिंदु ठीक काम करता है, लेकिन आप देख सकते हैं कि HTTP पद्धति का उपयोग करने के लिए कोई फर्क नहीं पड़ता, यह हमेशा उसी तरह से प्रतिक्रिया करता है।
उदाहरण के लिए:
curl -X POST localhost:8000/api/hello curl -X PUT localhost:8000/api/hello
पहली बात यह है कि तरीकों के बीच अंतर करने के लिए कोड जोड़ना है, उदाहरण के लिए:
server.createContext("/api/hello", (exchange -> { if ("GET".equals(exchange.getRequestMethod())) { String respText = "Hello!"; exchange.sendResponseHeaders(200, respText.getBytes().length); OutputStream output = exchange.getResponseBody(); output.write(respText.getBytes()); output.flush(); } else { exchange.sendResponseHeaders(405, -1);
क्वेरी फिर से आज़माएं:
curl -v -X POST localhost:8000/api/hello
जवाब कुछ इस तरह होगा:
> POST /api/hello HTTP/1.1 > Host: localhost:8000 > User-Agent: curl/7.61.0 > Accept: *
ध्यान में रखने के लिए कुछ बिंदु भी हैं। उदाहरण के लिए,
OutputStream
लिए
flush()
और
exchange
लिए
close()
करना न भूलें। स्प्रिंग का उपयोग करते समय, मुझे इसके बारे में सोचना भी नहीं चाहिए।
चरण -3 शाखा में स्रोत कोड।
पार्सिंग अनुरोध पैरामीटर
पार्सिंग क्वेरी पैरामीटर एक और "फ़ंक्शन" है जिसे हमें अपने दम पर लागू करने की आवश्यकता है।
मान लें कि हम चाहते हैं कि हमारा हैलो एपीआई
name
पैरामीटर में एक नाम प्राप्त करे, उदाहरण के लिए:
curl localhost:8000/api/hello?name=Marcin Hello Marcin!
हम निम्नानुसार मापदंडों को पार्स कर सकते हैं:
public static Map<String, List<String>> splitQuery(String query) { if (query == null || "".equals(query)) { return Collections.emptyMap(); } return Pattern.compile("&").splitAsStream(query) .map(s -> Arrays.copyOf(s.split("="), 2)) .collect(groupingBy(s -> decode(s[0]), mapping(s -> decode(s[1]), toList()))); }
और नीचे के रूप में उपयोग करें:
Map<String, List<String>> params = splitQuery(exchange.getRequestURI().getRawQuery()); String noNameText = "Anonymous"; String name = params.getOrDefault("name", List.of(noNameText)).stream().findFirst().orElse(noNameText); String respText = String.format("Hello %s!", name);
चरण -4 शाखा में पूर्ण उदाहरण।
इसी तरह, यदि हम पथ में मापदंडों का उपयोग करना चाहते हैं। उदाहरण के लिए:
curl localhost:8000/api/items/1
आईडी = 1 द्वारा तत्व प्राप्त करने के लिए, हमें स्वयं यूआरएल को पार्स करने की आवश्यकता है। यह भारी हो जाता है।
सुरक्षा
अक्सर हमें कुछ समापन बिंदुओं तक पहुंच की रक्षा करने की आवश्यकता होती है। उदाहरण के लिए, यह मूल प्रमाणीकरण का उपयोग करके किया जा सकता है।
प्रत्येक HttpContext के लिए, हम एक प्रमाणक सेट कर सकते हैं, जैसा कि नीचे दिखाया गया है:
HttpContext context = server.createContext("/api/hello", (exchange -> {
BasicAuthenticator
कंस्ट्रक्टर में "myrealm" का मूल्य नाम है। दायरे एक आभासी नाम है जिसका उपयोग प्रमाणीकरण डोमेन को अलग करने के लिए किया जा सकता है।
आप
RFC 1945 में इसके बारे में अधिक पढ़ सकते हैं।
अब आप
Authorization
शीर्षलेख जोड़कर इस सुरक्षित समापन बिंदु को कॉल कर सकते हैं:
curl -v localhost:8000/api/hello?name=Marcin -H 'Authorization: Basic YWRtaW46YWRtaW4='
"बेसिक" के बाद का पाठ बेस 64-एनकोडेड
admin:admin
टेक्स्ट, जो कि हमारे उदाहरण में क्रेडेंशियल्स हार्डकोड है।
एक वास्तविक एप्लिकेशन में प्रमाणीकरण के लिए, आपको संभवतः शीर्ष लेख से क्रेडेंशियल्स मिलेंगे और उनकी तुलना डेटाबेस में संग्रहीत उपयोगकर्ता नाम और पासवर्ड से करेंगे।
यदि आप एक शीर्षक निर्दिष्ट नहीं करते हैं, तो एपीआई एक स्थिति के साथ जवाब देगा
HTTP/1.1 401 Unauthorized
चरण -5 शाखा में पूर्ण उदाहरण।
JSON, अपवाद हैंडलिंग और बहुत कुछ
अब यह अधिक जटिल उदाहरण के लिए समय है।
सॉफ़्टवेयर डेवलपमेंट में मेरे अनुभव से, मैंने जो सबसे आम एपीआई विकसित किया वह JSON एक्सचेंज था।
हम नए उपयोगकर्ताओं के पंजीकरण के लिए एक एपीआई विकसित करने जा रहे हैं। उन्हें संग्रहीत करने के लिए, हम स्मृति में डेटाबेस का उपयोग करेंगे।
हमारे पास एक सरल
User
डोमेन ऑब्जेक्ट होगा:
@Value @Builder public class User { String id; String login; String password; }
बायलरप्लेट (कंस्ट्रक्टर, गेटर्स) से छुटकारा पाने के लिए मैं लोम्बोक का उपयोग करता हूं।
REST API में, मैं केवल लॉगिन और पासवर्ड पास करना चाहता हूं, इसलिए मैंने एक अलग ऑब्जेक्ट बनाया:
@Value @Builder public class NewUser { String login; String password; }
User
ऑब्जेक्ट उस सेवा में बनाए जाते हैं जिसे हम एपीआई हैंडलर में उपयोग करेंगे। सेवा पद्धति केवल उपयोगकर्ता को बचाती है।
public String create(NewUser user) { return userRepository.create(user); }
आप एक वास्तविक एप्लिकेशन में अधिक कर सकते हैं। उदाहरण के लिए, सफल उपयोगकर्ता पंजीकरण के बाद ईवेंट भेजें।
भंडार कार्यान्वयन निम्नानुसार है:
import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import com.consulner.domain.user.NewUser; import com.consulner.domain.user.User; import com.consulner.domain.user.UserRepository; public class InMemoryUserRepository implements UserRepository { private static final Map USERS_STORE = new ConcurrentHashMap(); @Override public String create(NewUser newUser) { String id = UUID.randomUUID().toString(); User user = User.builder() .id(id) .login(newUser.getLogin()) .password(newUser.getPassword()) .build(); USERS_STORE.put(newUser.getLogin(), user); return id; } }
अंत में, सब कुछ
handle()
में एक साथ गोंद
handle()
:
protected void handle(HttpExchange exchange) throws IOException { if (!exchange.getRequestMethod().equals("POST")) { throw new UnsupportedOperationException(); } RegistrationRequest registerRequest = readRequest(exchange.getRequestBody(), RegistrationRequest.class); NewUser user = NewUser.builder() .login(registerRequest.getLogin()) .password(PasswordEncoder.encode(registerRequest.getPassword())) .build(); String userId = userService.create(user); exchange.getResponseHeaders().set(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON); exchange.sendResponseHeaders(StatusCode.CREATED.getCode(), 0); byte[] response = writeResponse(new RegistrationResponse(userId)); OutputStream responseBody = exchange.getResponseBody(); responseBody.write(response); responseBody.close(); }
यहां, JSON अनुरोध को
RegistrationRequest
ऑब्जेक्ट में बदल दिया गया है:
@Value class RegistrationRequest { String login; String password; }
डेटाबेस में इसे सहेजने और JSON के रूप में प्रतिक्रिया भेजने के लिए मैंने बाद में
NewUser
ऑब्जेक्ट पर मैप किया।
मुझे भी
RegistrationResponse
ऑब्जेक्ट को JSON स्ट्रिंग में बदलने की आवश्यकता है। इसके लिए हम जैक्सन का उपयोग करते हैं
(
com.fasterxml.jackson.databind.ObjectMapper
)।
यह है कि मैं
main()
में नया हैंडलर कैसे बनाऊं:
public static void main(String[] args) throws IOException { int serverPort = 8000; HttpServer server = HttpServer.create(new InetSocketAddress(serverPort), 0); RegistrationHandler registrationHandler = new RegistrationHandler(getUserService(), getObjectMapper(), getErrorHandler()); server.createContext("/api/users/register", registrationHandler::handle);
चरण -6 शाखा में एक कार्यकारी उदाहरण पाया जा सकता है। वहां मैंने मानक JSON त्रुटि संदेश भेजने के लिए एक वैश्विक अपवाद हैंडलर भी जोड़ा। उदाहरण के लिए, यदि HTTP विधि समर्थित नहीं है या एपीआई के लिए अनुरोध सही तरीके से नहीं बना है।
आप एप्लिकेशन को चला सकते हैं और निम्नलिखित प्रश्नों में से एक आज़मा सकते हैं:
curl -X POST localhost:8000/api/users/register -d '{"login": "test" , "password" : "test"}'
का जवाब:
{"id":"395eab24-1fdd-41ae-b47e-302591e6127e"}
curl -v -X POST localhost:8000/api/users/register -d '{"wrong": "request"}'
का जवाब:
< HTTP/1.1 400 Bad Request < Date: Sat, 29 Dec 2018 00:11:21 GMT < Transfer-encoding: chunked < Content-type: application/json < * Connection #0 to host localhost left intact {"code":400,"message":"Unrecognized field \"wrong\" (class com.consulner.app.api.user.RegistrationRequest), not marked as ignorable (2 known properties: \"login\", \"password\"])\n at [Source: (sun.net.httpserver.FixedLengthInputStream); line: 1, column: 21] (through reference chain: com.consulner.app.api.user.RegistrationRequest[\"wrong\"])"}
इसके अलावा, मैं गलती से
जावा-एक्सप्रेस परियोजना में भाग गया, जो नोड के लिए
एक्सप्रेस ढांचे का एक जावा समकक्ष है। यह
jdk.httpserver
का भी उपयोग करता है, इसलिए आप इस लेख में वर्णित सभी अवधारणाओं को एक वास्तविक रूपरेखा पर सीख सकते हैं, जो इसके कोड का अध्ययन करने के लिए काफी छोटा है।