ááœááºáá²á·áá²á· áá«ážááŸá
áºáá±á¬ááºá á
áá²á·áááºá
á€áá±á¬ááºážáá«ážááẠGo ááœááºáá¬áá¬áá áºáá¯áá±ážáá¬ážááŒááºážá¡ááŒá±á¬ááºážááŒá áºáááºá "Hello world!" áá²á·ááá¯á·áá±á¬ ááá¯ážááŸááºážáá±á¬á¡áá¬áá»á¬ážááŒáá·áº á áááºááŒá®áž á¡á±á¬ááºáá«á áœááºážáááºáá»á¬ážááŒáá·áº á¡ááá®áá±ážááŸááºážáá áºáá¯ááŒáá·áº á¡áá¯á¶ážáááºááŒáá«á áá¯á·á
- HTTPS á¡ááœáẠLet's Encrypt ááᯠá¡áá¯á¶ážááŒá¯ááŒááºážá
- API Router á¡ááŒá
Ạá¡áá¯ááºáá¯ááºááŒááºážá
- á¡áááºáááºážáá±á¬á·ááºáá²ááŒáá·áº á¡áá¯ááºáá¯ááºááŒááºážá
- static ááá¯ááºáá»á¬ážááá¯áá¯ááºáá±á¬ááºááŒááºážá
- ááŸááºáááºáá±á¬ááááºááŒááºážá
Skillbox á០á¡ááŒá¶ááŒá¯áá¬ážáááº- áááºááœá±á·áááºáááºáž
"Python developer ááẠá¡á ááŸáá±á" .áá«ááá¯á·áááºážááá¯ááááá±ážááẠ"Habr" áá á¬áááºáá°á¡á¬ážáá¯á¶ážá¡ááœáẠ- "Habr" áááá¯ááá¯ážááŸááºážáá¯ááºááᯠá¡áá¯á¶ážááŒá¯á áááºááá·áº Skillbox áááºáááºážááœááºá á¬áááºážááœááºážááá·áºá¡áá« 10 áá°áááºáá»áŸá±á¬á·á á»á±ážá
áááºá¹ááá¬áá«ááá¹áá¬áá±á¬á!
Go ááœáẠáááºáá¬áá¬áá áºáá¯ááᯠáááºáááºáá®ážááá¯ááºáááºá á€áááºááŸá¬ á¡áááºáá±á¬áºááŒáá« áááááŒá¯áá¬ážááá·áº âHello, world!â ááᯠááŒááºáá±ážááá·áº handler ááá¯á¡áá¯á¶ážááŒá¯ááŒááºážá á¥ááá¬áá áºáá¯ááŒá áºáááºá
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.ListenAndServe(":80", nil)
}
ááŒá®ážááœá¬ážáááºáá±á¬á· application ááᯠrun ááŒá®áž page ááá¯ááœáá·áºááá¯ááºáá«á
áá»áœááºá¯ááºááá¯á·ááẠáá±á¬ááºááá¯ááºážááœáẠááá¯ááºááœááºáááááá¬ááᯠá¡ááŒáááºáá»á¬ážá áœá¬ á¡áá¯á¶ážááŒá¯áá«áááºá ááá¯á·áá±á¬áº á¡áá¬á¡á¬ážáá¯á¶áž áááºááá¯á·á¡áá¯ááºáá¯ááºáááºááᯠáŠážá áœá¬áá¬ážáááºááŒáá«á áá¯á·á
net/http
á¥ááá¬á package ááá¯áá¯á¶ážáááºá net/http
HTTP ááá¯ááºááœááºáá°áá»á¬áž
áá»áœááºá¯ááºááá¯á· áá±á¬ááºážááá¯áá»ááºááᯠáááºáá¶áááŸááá±á¬á¡áá«á ááá¯ááºááœááºáá°ááẠáááºážááᯠááá¯ááºážááŒá¬ážá áááºááŒá¬ááŒá®áž áá¯á¶á·ááŒááºááŸá¯ááᯠáá¯ááºáá±ážáá«áááºá Go in Handlers áá»á¬ážááᯠá¡á±á¬ááºáá«á¡ááá¯ááºáž á¡áá±á¬ááºá¡áááºáá±á¬áºáááº-
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
áááá¥ááá¬ááœáẠhttp.HandleFunc helper function ááá¯á¡áá¯á¶ážááŒá¯áááºá áááºážááẠhttp.ResponseWriter ááŸáá·áº http.Request ááᯠServeHTTP ááá¯á· áá°áá±á¬ááºááœá¬ážááá·áº á¡ááŒá¬ážáá±á¬áá¯ááºáá±á¬ááºáá»ááºááᯠááŒá¯á¶áá¯á¶áááá«áááºá
áá áºáááºážááá¯ááá±á¬áºá Golang ááŸá handlers áá»á¬ážááᯠáááá¯ááááºáá¬á¡ááœáẠááœá±ážáá»ááºá áá¬áá»á¬ážá áœá¬áá±ážááá·áº áá áºáá¯áááºážáá±á¬ áá»ááºááŸá¬ááŒááºáá áºáá¯ááœáẠááŒááá¬ážáááºá ááá¯á·ááŒá±á¬áá·áºá á¥ááá¬á¡á¬ážááŒáá·áºá ServeHTTP áá áºáá¯áá¯ááᯠáŠážá áœá¬áá¯ááºáá±á¬ááºááŒá®áž á¡ááŒá¬ážááá¯ááºááœááºáá°á ServeHTTP áááºážáááºážááᯠáá±á«áºááá·áº ááá¯ááºááœááºáááááá¬ááᯠá¡áá¯á¶ážááŒá¯á á¡áááºáááºážáá±á¬á·ááºáá²ááᯠá¡áá±á¬ááºá¡áááºáá±á¬áºáááºá
á¡áááºááœááºáá±á¬áºááŒáá²á·ááá·áºá¡ááá¯ááºážá ááá¯ááºááœááºáá°áá»á¬ážááẠáá±á¬ááºážááá¯áá»ááºáá»á¬ážááᯠáá¯á¶á·ááŒááºááŸá¯áá»á¬ážááᯠááá¯ážááŸááºážá áœá¬áá¯ááºáá±ážáá«áááºá ááá¯á·áá±á¬áº á¡áá»áááºáá¬ááá áºáá¯ááœáẠáááºááá·áºáááºááá¯ááºáááááá¬ááᯠá¡áá¯á¶ážááŒá¯ááá·áºááááºážá
áááºážááŒá±á¬ááºážáá±á¬ááºážááá¯ááŒááºážá
ááŸááºáááºáá±á¬ááœá±ážáá»ááºááŸá¯ááŒá¯áá¯ááºááẠHTTP multiplexer ááᯠá¡áá¯á¶ážááŒá¯áá«á á á¬ááŒáá·áºááá¯ááºáá»á¬ážá áœá¬ááœáẠáááºážááᯠmuxer ááá¯á·ááá¯áẠrouter áá¯áá±á«áºááœááºáá±á¬áºáááºáž áááºážááá¯á·á¡á¬ážáá¯á¶ážááẠá¡áá°áá°áááºááŒá áºáááºá multiplexer ááá¯ááºáá±á¬ááºáá»ááºááŸá¬ áá±á¬ááºážááá¯ááŸá¯áááºážááŒá±á¬ááºážááá¯ááœá²ááŒááºážá áááºááŒá¬ááŒá®áž ááá·áºáá»á±á¬áºáá±á¬ááá¯ááºááœááºáá°ááá¯ááœá±ážáá»ááºáááºááŒá áºáááºá
á¡áááºá áááºááẠááŸá¯ááºááœá±ážáá±á¬áááºážááŒá±á¬ááºážáááºááŒááºážá¡ááœáẠáá¶á·ááá¯ážááŸá¯ááá¯á¡ááºáá«áá ááŒááºáá¡ááœá²á·á¡á
ááºážá
á¬ááŒáá·áºááá¯ááºáá»á¬ážááᯠá¡áá¯á¶ážááŒá¯ááŒááºážááẠááá¯áá±á¬ááºážáá«áááºá á¡áá»áá¯á·áá±á¬ á¡ááá·áºááŒáá·áºáá¯á¶áž-
áá¯á¶ááŸááºá¡ááŒá±á¡áá±áá áºáá¯ááœáẠááŸá¯ááºááœá±ážáá±á¬áá±á¬ááºáá»á¬ážááŸáá·áºá¡áá° á¡áá¯ááºáá¯ááºááŒááºážááẠá á¶ááá¯ááºáá±á¬ááŒá±ááŸááºážáááºážáá»á¬áž ááá¯á¡ááºáááºááŒá áºááŒá®áž áááºážááẠáá¯á¶áá±ááá¯ááºááœááºáá°á¡áá¯á¶ážááŒá¯ááŸá¯ááᯠáááááá¬áá¬ááŸá¯ááºááœá±ážá á±áááºá á¡ááá®áá±ážááŸááºáž á¡áá»á¬ážá á¯ááᯠáááºáá®ážáááºá áá¯á¶áá±á á¬ááŒáá·áºááá¯ááºááŸáá·áº ááá¯ážááá¯áž router áá±á«ááºážá ááºááŸá¯ áá¯á¶áá±á¬ááºáááºááŒá áºáááºá
áá±ážááŒááºážááŸá¯ áá¯ááºáá±á¬ááºááŒááºážá
ááá¯á·á¡ááŒááºá á¡áááºáá»áááºáááºááŸá¯áá»á¬ážá¡ááœáẠ"áá¬ážáááº" ááá·áº á¡á áááºá¡ááá¯ááºážáá áºáᯠááá¯á¡ááºááŒá®áž áá±á¬ááºážááá¯áá»ááºá¡á¬ážáá¯á¶ážááᯠááŸááºáááºáá±á¬ ááá¯ááºááœááºáá°áᶠááŒááºááœáŸááºážáá±ážááá·áº á¡á áááºá¡ááá¯ááºážáá áºáᯠááá¯á¡ááºáá«áááºá http.Server ááẠá€áá¯ááºáááºážááᯠá¡ááœááºááá° ááá¯ááºááœááºááá¯ááºáááºá
áá»áááºáááºááŸá¯ áá¯ááºáá±á¬ááºááŒááºážááŸáá·áº áááºá ááºááá·áº á¡áá¯ááºá¡á¬ážáá¯á¶ážá¡ááœáẠáá¬áá¬ááẠáá¬áááºááŸáááŒá±á¬ááºáž á¡á±á¬ááºáá«ááá¯á·á ááŒááááºá á¥ááá¬á áááºážááẠTLS áááá¯ááá¯áá±á¬ááᯠá¡áá¯á¶ážááŒá¯á á¡áá¯ááºáá¯ááºáááºá http.ListenAndServer áá±á«áºááá¯ááŸá¯ááᯠá¡áá±á¬ááºá¡áááºáá±á¬áºáááºá áá¯á¶ááŸáẠHTTP áá¬áá¬ááᯠá¡áá¯á¶ážááŒá¯áááºá
ááᯠááá¯ááá¯ááŸá¯ááºááœá±ážáá±á¬ á¥ááá¬áá»á¬ážááᯠááŒáá·áºááŒáá«á áá¯á·á
á á¬ááŸááºááá·áºááŒáá«á áá¯á·
áá°áááºážá¡á¬ážááŒáá·áºá áá»áœááºá¯ááºááá¯á·á á¡ááá®áá±ážááŸááºážááẠHTTP áááá¯ááá¯áá±á¬ááᯠáá»á±á¬áºá áá¯ááºáá±á¬ááºáá±á¬áºáááºáž HTTPS áááá¯ááá¯áá±á¬ááᯠá¡áá¯á¶ážááŒá¯ááẠá¡ááŒá¶ááŒá¯áá¬ážáááºá Go ááœáẠááŒá¿áá¬áááŸááá² áááºážááᯠáá¯ááºáá±á¬ááºááá¯ááºáááºá á¡áááºá áááºááẠáááºááŸááºááŸáá·áº áá®ážááá·áºáá±á¬á·ááᯠáááºáá¶áááŸááá«áá ááŸááºáááºáá±á¬ áááºááŸááºááŸáá·áº áá±á¬á·ááá¯ááºáá»á¬ážááŒáá·áº ListenAndServeTLS ááᯠá á¬áááºážááœááºážááẠáá¯á¶áá±á¬ááºáá«áááºá
http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)
á¡ááŒá²áááºáž ááá¯áá±á¬ááºážá¡á±á¬ááºáá¯ááºááá¯ááºáááºá
autocert
.
áááºážááᯠconfigure áá¯ááºááẠá¡ááœááºáá°áá¯á¶ážáááºážáááºážááŸá¬ http.Serve ááŸáá·áº áá±á«ááºážá ááºáá¬ážáá±á¬ autocert.NewListener áááºážáááºážááᯠá¡áá¯á¶ážááŒá¯ááŒááºážááŒá áºáááºá HTTP áá¬áá¬á០áá±á¬ááºážááá¯ááŸá¯áá»á¬ážááᯠáá¯ááºáá±á¬ááºáá±áá»áááºááœáẠáááºážáááºážááẠááá·áºá¡á¬áž TLS áááºááŸááºáá»á¬ážááᯠááá°ááŒá®áž á¡ááºááááºáá¯ááºááẠááœáá·áºááŒá¯áááº-
http.Serve(autocert.NewListener("example.com"), nil)
ááá±á¬ááºáá¬ááŸá¬ ááœáá·áºááá¯ááºáááº
áááºááá¯ááá¯á¡áá±ážá áááºááœá²á·á ááºážáá¯á¶ááá¯á¡ááºáá«áá autocert.Manager áááºáá±áá»á¬ááᯠá¡áá¯á¶ážááŒá¯ááá·áºáááºá ááá¯á·áá±á¬áẠáá»áœááºá¯ááºááá¯á·ááẠáá»áœááºá¯ááºááá¯á·áááá¯ááºááá¯áẠhttp.Server instance ááá¯áááºáá®ážááŒá®áž (ááá¯áá»áœááºá¯ááºááá¯á·áá°áá¡á¬ážááŒáá·áº áááºážááá¯á¡áá¯á¶ážááŒá¯áá²á·áááº) ááŸáá·áº áááºáá±áá»á¬ááᯠTLSConfig áá¬áá¬ááá¯á· ááá·áºáá«-
m := &autocert.Manager{
Cache: autocert.DirCache("golang-autocert"),
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
}
server := &http.Server{
Addr: ":443",
TLSConfig: m.TLSConfig(),
}
server.ListenAndServeTLS("", "")
áááºážááẠá¡ááá¯á¡áá»á±á¬áẠáááºááŸáẠáááºáááºážááá¯ážááŒááºážááŒáá·áº HTTPS áá¶á·ááá¯ážááŸá¯ á¡ááŒáá·áºá¡á á¯á¶ááᯠá¡áá±á¬ááºá¡áááºáá±á¬áºááẠááœááºáá°áá±á¬ áááºážáááºážááŒá áºáááºá
á áááºááŒáá¯ááºáááºážááŒá±á¬ááºážáá»á¬ážááá·áºááŒááºážá
á á¶á á¬ááŒáá·áºááá¯ááºááœáẠáá«áááºáá±á¬ áá°áááºáž router ááẠáá±á¬ááºážááœááºáá±á¬áºáááºáž áááºážááẠá¡ááœááºá¡ááŒá±áá¶áááºá á¡ááá®áá±ážááŸááºážá¡áá»á¬ážá á¯ááẠnested ááŸáá·áº wildcard áááºážááŒá±á¬ááºážáá»á¬áž á¡áá«á¡ááẠááá¯ááá¯ááŸá¯ááºááœá±ážáá±á¬áááºážááŒá±á¬ááºážáá»á¬áž ááá¯á¡ááºááẠááá¯á·ááá¯áẠáááºážááŒá±á¬ááºážáá¯á¶á á¶áá»á¬ážááŸáá·áº ááá·áºáááºáá»ááºáá»á¬ážááᯠáááºááŸááºáááºá¡ááœáẠáá¯ááºáá¯á¶ážáá¯ááºáááºážáá áºáᯠááá¯á¡ááºáá«áááºá
á€ááá
á¹á
ááœááºáááºážááẠpackages áá»á¬ážááá¯á¡áá¯á¶ážááŒá¯ááá»áá¯ážáááºáááºá
áá»áœááºá¯ááºááá¯á·á API á¡ááœáẠáááºážááŒá±á¬ááºážáá»á¬ážáá«ááŸááá±á¬ api/v1/api.go ááá¯ááºááᯠáá±ážáá¬ážáá«áááºá
/ HelloResponse is the JSON representation for a customized message
type HelloResponse struct {
Message string `json:"message"`
}
// HelloName returns a personalized JSON message
func HelloName(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
response := HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}
jsonResponse(w, response, http.StatusOK)
}
// NewRouter returns an HTTP handler that implements the routes for the API
func NewRouter() http.Handler {
r := chi.NewRouter()
r.Get("/{name}", HelloName)
return r
}
áá»áœááºá¯ááºááá¯á·ááẠáááºáááá¯ááºááŸá áááºážááŒá±á¬ááºážáá»á¬ážá¡ááœáẠapi/vq ááŸá±á·áááºááᯠáááºááŸááºáá±ážáááºá
ááá¯á·áá±á¬áẠáá»áœááºá¯ááºááá¯á·ááẠáá»áœááºá¯ááºááá¯á·ááááºáá¡ááá®áá±ážááŸááºážááœáẠapi/v1/ prefix á¡á±á¬ááºááŸá áá»áœááºá¯ááºááá¯á·ááááºá router ááá¯á· áááºáááºááá¯ááºáááº-
// NewRouter returns a new HTTP handler that implements the main server routes
func NewRouter() http.Handler {
router := chi.NewRouter()
router.Mount("/api/v1/", v1.NewRouter())
return router
}
http.Serve(autocert.NewListener("example.com"), NewRouter())
Go áááŸá¯ááºááœá±ážáá±á¬áááºážááŒá±á¬ááºážáá»á¬ážááŸáá·áºá¡áá¯ááºáá¯ááºáááœááºáá°ááŒááºážáááºááŒá®ážáá¬ážááŒá®ážááŸá¯ááºááœá±ážáá±á¬á¡áá¯á¶ážáá»áááá¯ááááºáá»á¬ážáááœá²á·á ááºážáá¯á¶ááŸáá·áºááááºážááááºážááŸá¯ááá¯ááá¯ážááŸááºážááœááºáá°á á±áááºá
á¡áááºáááºážáá±á¬á·ááºáá²ááŒáá·áº á¡áá¯ááºáá¯ááºáááºá
Staging ááœáẠHTTP handler áá áºáá¯á¶ážááᯠá¡ááŒá¬ážáá áºáá¯ááŸáá·áº áááºáá¬ážááŒááºážááŒáá·áº authenticationá compressioná logging ááŸáá·áº á¡ááŒá¬ážáá±á¬ function á¡áá»á¬ážá¡ááŒá¬ážááᯠáá»ááºááŒááºá áœá¬ áá¯ááºáá±á¬ááºááá¯ááºá á±áá«áááºá
á¥ááá¬á¡áá±áá²á·á http.Handler á¡ááºáá¬áá±á·á áºááᯠááŒáá·áºáá¡á±á¬ááºá áááºáá±á¬ááºááŸá¯á¡áá¯á¶ážááŒá¯áá°ááœá±ááᯠá á áºááŸááºááŒá±á¬ááºážá¡áá±á¬ááºá¡áá¬ážááŒáá²á· áááºááá¯ááºááá¯áá±ážááá¯á· á¡áá¯á¶ážááŒá¯áá«áááºá
func RequireAuthentication(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !isAuthenticated(r) {
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
return
}
// Assuming authentication passed, run the original handler
next.ServeHTTP(w, r)
})
}
á¡áááºáááºážáá±á¬á·ááºáá²áá¯ááºáá±á¬ááºááá¯ááºá áœááºážááᯠááá¯ážáá»á²á·ááœáá·áºááŒá¯ááá·áº Chi áá²á·ááá¯á·áá±á¬ ááŒááºáá¡ááœá²á·á¡á ááºážáá±á¬ááºáá¬áá»á¬ážááŸááááºá
static ááá¯ááºáá»á¬ážááŸáá·áºá¡áá¯ááºáá¯ááºááŒááºážá
Go á á¶ááŒá á¬ááŒáá·áºááá¯ááºááœáẠáá¯á¶áá»á¬ážá JavaScript ááŸáá·áº CSS ááá¯ááºáá»á¬ážá¡áá«á¡ááẠáááºááŒáááºáá±á¬á¡ááŒá±á¬ááºážá¡áá¬ááŒáá·áº áá¯ááºáá±á¬ááºááá¯ááºáá±á¬ á áœááºážáááºáá»á¬áž áá«áááºáááºá áááºážááá¯á·ááᯠhttp.FileServer áá¯ááºáá±á¬ááºáá»ááºááŒáá·áº áááºáá±á¬ááºááŒáá·áºááŸá¯ááá¯ááºáá«áááºá áááºážááẠáá®ážááŒá¬ážáááºážááœáŸááºáá áºáá¯á០ááá¯ááºáá»á¬ážááᯠáá±á¬ááºááœááºáá±ážááá·áº ááá¯ááºááœááºáá°ááᯠááŒááºáá±ážáááºá
func NewRouter() http.Handler {
router := chi.NewRouter()
r.Get("/{name}", HelloName)
// ÐаÑÑÑПйка ÑазЎаÑО ÑÑаÑОÑеÑкОÑ
ÑайлПв
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
router.Handle("/*", fs)
return r
http.Dir ááẠáááºá index.html ááá¯ááºááá«áááºáá«á áááºážááœáŸááºá á¡ááŒá±á¬ááºážá¡áá¬áá»á¬ážááᯠááŒááááºááŸá¬ áá±áá»á¬áá«áááºá á€ááá
á¹á
ááœááºá áááºážááœáŸááºááá¯á¡ááá¯ážáá¶áááŒááºážááŸáá¬ááœááºáááºá áááºá¡áá¯ááºááá¯á¡áá¯á¶ážááŒá¯ááá·áºáááºá unindexed
ááŸááºáááºáá±á¬ááááºááŒááºážá
Go ááœáẠHTTP áá¬áá¬ááᯠáááºáááºá áœá¬ááááºááŒááºážáá¯áá±á«áºááá·áº á¡ááºá¹áá«áááºáá áºáá¯áááºáž áá«ááŸááááºá Shutdown() method ááᯠá¡áá¯á¶ážááŒá¯á áá¯ááºáá±á¬ááºááá¯ááºáá«áááºá áá¬áá¬ááᯠááá¯ááá¯ážáááºážáá¯á¶á á¶ááŒáá·áº á áááºáá¬ážááŒá®ážá ááá¯á·áá±á¬ááºááœáẠááŒá¬ážááŒááºá¡áá»ááºááŒááŸá¯ááᯠáááºáá¶áááŸáááẠáá»ááºáááºááᯠáá¬ážáá±á¬ááºáááºá á¡áá»ááºááŒááŒááºážááᯠáááºáá¶áááŸááááºááŸáá·áº áááŒáá¯ááºááẠáá¬áá¬ááẠááááºááœá¬ážáá±á¬áºáááºáž áá»ááºáá»ááºážááá¯ááºáá±á¬áºáááºáž á áá¹ááá·áºá¡áááºážáááºááŒá¬ááŒá®ážáá±á¬ááºá
handler := server.NewRouter()
srv := &http.Server{
Handler: handler,
}
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
// Wait for an interrupt
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
// Attempt a graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)
áááá¯á¶ážáá»á¯ááº
Go ááẠá ááŒáá ᬠá á¶ááŒá á¬ááŒáá·áºááá¯ááºáá«ááŸááá±á¬ á¡á¬ážáá±á¬ááºážáá±á¬ááºážáááºáá¬áá¬á áá¬ážáá áºáá¯ááŒá áºáááºá áááºážááá°áá áœááºážáááºáá»á¬ážááẠá¡ááœááºáá»ááºááŒáá·áºááŒá®áž áááºážááá¯á·ááᯠá¡ááºáá¬áá±á·á áºáá»á¬ážá¡áá¯á¶ážááŒá¯á ááŒáŸáá·áºáááºááá¯ááºááẠ- áááºážááẠááá·áºá¡á¬áž á¡ááŸááºááááºáá¯á¶ááŒááºá áááºáá»ááá±á¬ HTTP áá¬áá¬áá»á¬ážááᯠáá®ááœááºááá¯ááºá á±áááºááŒá áºáááºá
Skillbox á០á¡ááŒá¶ááŒá¯áá¬ážáááº-
- ááŸá áºááŸá áºá á¬áááºááœá±á·áááºáááºáž
"áá»áœááºáá±á¬áºá PRO ááẠdeveloper áá áºáá±á¬ááºáá«" .- ááá¬áá±ážá¡áœááºááá¯ááºážáááºáááºáž
"Profession Java developer" .- áááºááœá±á· áá áºááŸá áºáááºáááºáž
"PHP developer 0 á០PRO" .
source: www.habr.com