Në thelb të komunikimit rrjetor në Go është pakoja e quajtur net
. Kjo pako ofron implementimet për HTTP klient dhe server.
Pakoja net
përmban nënpako jo vetëm për HTTP operacionet relevante, por po ashtu edhe për serverët TCP/UDP, DNS dhe IP vegla.
hello.go
package main
import (
"fmt"
"log"
"net/http"
"time"
)
const (
Port = ":8080"
)
func main() {
http.HandleFunc("/statike", faqeStatike)
http.HandleFunc("/dinamike", faqeDinamike)
log.Fatal(http.ListenAndServe(Port, nil))
}
func faqeStatike(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "dokumenti.html")
}
func faqeDinamike(w http.ResponseWriter, r *http.Request) {
response := "Ora është " + time.Now().String()
fmt.Fprintln(w, response)
}
https://play.golang.org/p/dSSZS2VPxYh
Rezultati shfaqet në browser, duke e thirrur me rutat /dinamike
për përmbajtjen dinamike që do të jetë shfaqja e kohës aktuale dhe /statike
për përmbajtjen statike që në rastin konkret do të jetë një HTML dokument. Kodi duhet të kompajlohet: go run hello.go
.
dokumenti.html
<!DOCTYPE html>
<html>
<head>
<title>Your Title Here</title>
</head>
<body>
<H1>Ky eshte nje titull</H1>
<p>Ky eshte nje paragraf.</p>
</body>
</html>
Si port për Web server mund ta përdorim cilindo port, ndërsa në shembuj do ta përdorim portin 8080.
http.HandleFunc
Me http.HandleFunc
i definojmë rutat e dëshiruara, duke e cekur si parametër të parë rutën, ndërsa si parametër të dytë funksionin i cili i korrespondon asaj rute.
http.HandleFunc("/statike", faqeStatike)
http.ServeFile
Me http.ServeFile
mund të dërgojmë një përmbajtje statike si reagim ndaj kërkesës.
Në rreshtin http.HandleFunc("/statike", faqeStatike)
përcaktojmë që kur të kërkohet ruta /statike
të thirret funksioni faqeStatike
, i cili në këtë shembull ka për detyrë vetëm ta shfaqë një dokument statik me emrin dokumenti.html
.
func faqeStatike(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "dokumenti.html")
}
Nëpërmes variablit w
do të mund të shkruajmë HTTP reagimin (response), ndërsa nëpërmes variablit r do ta pranojmë HTTP kërkesën (request).
fmt.Fprintln(w, response)
Me fmt.Fprintln
dërgojmë përmbajtjen e dëshiruar si HTTP response, pra ajo përmbajtje do të jetë e qasshme në browser nëpërmes rutës së definuar, në rastin konkret /dinamike
.
func faqeDinamike(w http.ResponseWriter, r *http.Request) {
response := "Ora është " + time.Now().String()
fmt.Fprintln(w, response)
}
Radhitja e procesit të zhvillimit të Web aplikacionit
- E shkruajmë kodin në Go
- E krijojmë HTML dokumentin statik
- E kompajlojmë kodin dhe e ekzekutojmë
- Nëse lajmërohet anti-virusi, e lejojmë që ta skanojë .exe programin e sapokrijuar
- Nëse firewall kërkon leje për lejimin e portit, ia japim lejen
- Kalojmë në browserin e dëshiruar dhe në
address bar
shënojmë/statike
ose/dinamike
për ta parë përmbajtjen e këtyre dy rutave - Nëse bëjmë ndryshime, atëherë e ndërprejmë në terminal ekzekutimin e programit me
Ctrl-C
, e më pas e kompajlojmë dhe e ekzekutojmë sërish
Leximi i përmbajtjes së një Web faqeje
Me programin vijues do ta lexojmë përmbajtjen e një faqeje në adresë të caktuar dhe përmbajtjen e saj do ta shfaqim në terminal.
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
faqja, err := http.Get("http://example.com/")
if err != nil {
fmt.Println("Nuk munda ta hap faqen example.com")
}
defer faqja.Body.Close()
body, err := ioutil.ReadAll(faqja.Body)
fmt.Println(string(body))
}
https://play.golang.org/p/sr8tQd6jBs3
http.Get
Mundëson qasjen në përmbajtjen e një resursi në URL-në e specifikuar. Përmbajtja e kthyer do të jetë e tipit *http.Response
.
ioutil.ReadAll
func ReadAll(r io.Reader) ([]byte, error)
CRUD operacionet
Në shembullin vijues, do të krijohet një REST API i thjeshtë me 4 operacionet bazike ndaj databazës (CRUD), me koneksionin e databazës si variabël globale, në mënyrë që të mos krijohet koneksioni me databazën brenda çdo funksioni.
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"log"
"net/http"
"strings"
)
const (
DBHost = "127.0.0.1"
DBPort = ":3306"
DBUser = "root"
DBPass = ""
DBDbase = "golang"
)
var database *sql.DB
func main() {
dbConn := fmt.Sprintf("%s:%s@tcp(%s)/%s", DBUser, DBPass, DBHost, DBDbase)
db, err := sql.Open("mysql", dbConn)
if err != nil {
log.Println("Couldn't connect!")
log.Println(err.Error)
}
database = db
fmt.Println("Duke e startuar serverin")
http.HandleFunc("/book/", RHandler)
http.HandleFunc("/save/", CHandler)
http.HandleFunc("/delete/", DHandler)
http.HandleFunc("/update/", UHandler)
err = http.ListenAndServe(":8082", nil)
if err != nil {
panic(err)
}
}
func RHandler(w http.ResponseWriter, r *http.Request) {
url := strings.Split(r.URL.Path, "/")
sql := "SELECT id, book_title FROM books WHERE id='" + url[2] + "'"
var id int
var book_title string
var tabela string
rows, _ := database.Query(sql)
for rows.Next() {
rows.Scan(&id, &book_title)
tabela += book_title
}
fmt.Fprint(w, tabela)
}
func CHandler(w http.ResponseWriter, r *http.Request) {
s := r.URL.Query().Get("book_title")
sql := "INSERT INTO books SET book_title='" + s + "'"
database.Query(sql)
}
func DHandler(w http.ResponseWriter, r *http.Request) {
s := r.URL.Query().Get("id")
sql := "DELETE FROM books WHERE id='" + s + "'"
database.Query(sql)
}
func UHandler(w http.ResponseWriter, r *http.Request) {
url := strings.Split(r.URL.Path, "/")
id := url[2]
book_title := r.URL.Query().Get("book_title")
sql := "UPDATE books SET book_title= '" + book_title + "' WHERE id='" + id + "'"
database.Query(sql)
}
https://play.golang.org/p/srKlazK-9_S
Leximi i librit me ID 4:
Insertimi i një libri të ri:
http://localhost:8082/save/?book_title=Java+per+fillestare
Përditësimi i një libri ekzistues
http://localhost:8082/update/1/?book_title=Python+per+fillestare
Fshirja e një libri
http://localhost:8082/delete/?id=1
Sqarime:
Definojmë konstantat me vlerat për hostin, portin, përdoruesin, fjalëkalimin dhe emrin e databazës.
const (
DBHost = "127.0.0.1"
DBPort = ":3306"
DBUser = "root"
DBPass = ""
DBDbase = "golang"
)
Më pas, e definojmë një variabël globale me emrin database
të tipit *sql.DB
.
Në funksionin main(), e formatojmë stringun e koneksionit dhe e ruajmë në variablin dbConn
.
dbConn := fmt.Sprintf("%s:%s@tcp(%s)/%s", DBUser, DBPass, DBHost, DBDbase)
Bëjmë konektimin me MySQL server dhe verifikojmë nëse është shfaqur ndonj[ gabim:
db, err := sql.Open("mysql", dbConn)
if err != nil {
log.Println("Couldn't connect!")
log.Println(err.Error)
}
```
Vlerën e variablit `db` e bartim në variablin `database` të cilin më parë e kemi deklaruar si variabël globale. Tash e tutje, çfarëdo operacion ndaj databazës e kryejmë duke iu referuar variablit `database` nëpër funksione:
database.Query(sql)
ose
rows, _ := database.Query(sql)
## Struct / database query
Në shembullin vijues do të përdorim një strukt (Lajmi), të përbërë nga 4 fusha:
* Id,
* Titulli,
* Teksti, dhe
* Data.
Tabelën mund ta krijojmë duke e përdorur SQL kodin e mëposhtëm.
**lajmet.sql**
SET SQL_MODE = “NO_AUTO_VALUE_ON_ZERO”;
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = “+00:00”;
CREATE TABLE lajmet
(
id
int(11) NOT NULL,
titulli
varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
teksti
text COLLATE utf8mb4_unicode_ci NOT NULL,
data
timestamp NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE lajmet
ADD PRIMARY KEY (id
),
ADD KEY titulli
(titulli
),
ADD KEY data
(data
);
ALTER TABLE lajmet
MODIFY id
int(11) NOT NULL AUTO_INCREMENT;
COMMIT;
**Programi**
Për rutim më të avancuar, kësaj radhe do të përdoret pakoja `github.com/gorilla/mux`.
`routes.HandleFunc("/page/{id:[0-9]+}", ServePage)`
Me këtë kemi definuar se ruta do të jetë `/page/`, që do ta pranojë një parametër të cilit brenda funksionit do t'i referohemi si `id`. Id-ja do të jetë numër prej 0 deri 9 me një apo më tepër shifra. Kështu që një lajm do të mund të hapet me `/page/1`.
Nëse parametri i shënuar pas `/page/` nuk është numerik, pra kur nuk i përgjigjet pattern-it të definuar `{id:[0-9]+}`, do të lajmërohet gabimi:
`404 page not found`
Funksioni ServePage do ta pranojë id-në nga `r *http.Request` me:
vars := mux.Vars(r)
pageID := vars[“id”]
`mux.Vars(r)` përmban të gjitha GET variablat nga `request në formë të tipit `map`, prej nga me vars["id"] e ekstraktojmë vetëm fushën e `id`, të cilin pastaj e përdorim gjatë ndërtimit të `SQL query`.
SQL query thirret me `database.QueryRow()` ku shënojmë `SELECT id, titulli, teksti, data FROM lajmet WHERE id=?`, ndërsa id-në e faqes e japim si parametër të dytë. Me këtë parandalojmë `SQL injection`. Rezultati me Scan bartet në fushat korresponduese të struktit Lajmi.
Në `w http.ResponseWriter` me `fmt.Fprint()` e dërgojmë struktin si response. Ky nuk është format i përshtatshëm për response, sepse do të duhet ta dërgojmë si JSON ose si HTML dokument.
**Kodi i programit**
package main
import (
“database/sql”
“fmt”
_ “github.com/go-sql-driver/mysql”
“github.com/gorilla/mux”
“log”
“net/http”
)
const (
DBHost = “127.0.0.1”
DBPort = “:3306”
DBUser = “root”
DBPass = “”
DBDbase = “golang”
)
var database *sql.DB
type Lajmi struct {
Id int
Titulli string
Teksti string
Data string
}
func main() {
dbConn := fmt.Sprintf(“%s:%s@tcp(%s)/%s”, DBUser, DBPass, DBHost, DBDbase)
db, err := sql.Open(“mysql”, dbConn)
if err != nil {
log.Println(“Couldn’t connect!”)
log.Println(err.Error)
}
database = db
routes := mux.NewRouter()
routes.HandleFunc("/page/{id:[0-9]+}", ServePage)
http.Handle("/", routes)
http.ListenAndServe(":8080", nil)
}
func ServePage(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pageID := vars[“id”]
Lajm := Lajmi{}
database.QueryRow(“SELECT id, titulli, teksti, data FROM lajmet WHERE id=?”, pageID).Scan(&Lajm.Id, &Lajm.Titulli, &Lajm.Teksti, &Lajm.Data)
fmt.Fprint(w, Lajm)
}
<https://play.golang.org/p/RkvQdXGqT0e>
Leximi i një lajmi:
Në versionin ekzistues, nëse nuk gjendet një lajm me id të caktuar në tabelën e lajmeve, krejt çka raporton programi është një strukt i zbrazët.
Për të fituar një raport që tregon se lajmi nuk u gjet, e modifikojmë funksionin:
func ServePage(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pageID := vars[“id”]
Lajm := Lajmi{}
err := database.QueryRow(“SELECT id, titulli, teksti, data FROM lajmet WHERE id=?”, pageID).Scan(&Lajm.Id, &Lajm.Titulli, &Lajm.Teksti, &Lajm.Data)
if err != nil {
http.Error(w, http.StatusText(404), http.StatusNotFound)
log.Println(“Couldn’t get page!”)
} else {
fmt.Fprint(w, Lajm)
}
}
Nëse thirrja e QueryRow kthen gabim, atëherë në `w` kthejmë statusin 404, edhe si tekst, edhe si status kod numerik:
http.Error(w, http.StatusText(404), http.StatusNotFound)
Siç u cek më sipër, kthimi i rezultatit nga databaza u dërgua në `ResponseWriter` nuk ishte i formatuar në formë të përshtatshme. Tash do ta formatojmë si JSON.
Së pari e importojmë pakon `encoding/json`.
Konvertimi nga strukt në JSON kërkon që në strukt t'i definojmë JSON tags:
type Lajmi struct {
Id int json:"id"
Titulli string json:"titulli"
Teksti string json:"teksti"
Data string json:"data"
}
Konvertimi i struktit në JSON bëhet me metodën `json.Marshal()`:
b, err := json.Marshal(Lajm)
Tash `b` përmban `slice of bytes`, të cilin me funksionin string() e kthejmë në tekst të lexueshëm dhe e dërgojmë në `http.ResponseWriter` nëpërmes variablit `w`:
fmt.Fprint(w, string(b))
Kodi komplet:
package main
import (
“database/sql”
“encoding/json”
“fmt”
_ “github.com/go-sql-driver/mysql”
“github.com/gorilla/mux”
“log”
“net/http”
)
const (
DBHost = “127.0.0.1”
DBPort = “:3306”
DBUser = “root”
DBPass = “”
DBDbase = “golang”
)
var database *sql.DB
type Lajmi struct {
Id int json:"id"
Titulli string json:"titulli"
Teksti string json:"teksti"
Data string json:"data"
}
func main() {
dbConn := fmt.Sprintf(“%s:%s@tcp(%s)/%s”, DBUser, DBPass, DBHost, DBDbase)
db, err := sql.Open(“mysql”, dbConn)
if err != nil {
log.Println(“Couldn’t connect!”)
log.Println(err.Error)
}
database = db
routes := mux.NewRouter()
routes.HandleFunc("/page/{id:[0-9]+}", ServePage)
http.Handle("/", routes)
http.ListenAndServe(":8080", nil)
}
func ServePage(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pageID := vars[“id”]
Lajm := Lajmi{}
err := database.QueryRow(“SELECT id, titulli, teksti, data FROM lajmet WHERE id=?”, pageID).Scan(&Lajm.Id, &Lajm.Titulli, &Lajm.Teksti, &Lajm.Data)
if err != nil {
http.Error(w, http.StatusText(404), http.StatusNotFound)
log.Println(“Couldn’t get page!”)
} else {
b, err := json.Marshal(Lajm)
if err != nil {
fmt.Println(err)
} else {
fmt.Fprint(w, string(b))
}
}
}
<https://play.golang.org/p/pRlmmAfKEfL>
Tash nëpërmes thirrjes së rutës `http://localhost:8080/page/1`
do të fitojmë një JSON response sikur kjo:
{
“id”:1,
“titulli”:”Italia mposht Greqinë dhe siguron kualifikimin në Euro 2020\r\n”,
“teksti”:”Kombëtarja e Italisë ka siguruar kualifikimin në Kampionatin Evropian pas fitores 2-0 ndaj Greqisë në kuadër të ndeshjeve eliminatore të Grupit J.\r\n\r\nEkipi i drejtuar nga Roberto Mancini ka vazhduar serinë e qind për qind me shtatë fitore nga po kaq ndeshje, duke mposhtur në shtëpi Greqinë.\r\n\r\nPjesa e parë e ndeshjes u karakterizua me dominim të Azurrëve, por edhe përkundër rasteve të krijuara nuk arritën të gjejnë rrjetën.\r\n\r\nNë pjesën e dytë, përsëri ishte Italia që dominoi në posedim dhe raste, ndërsa shënoi edhe dy gola për të siguruar fitoren.\r\n\r\nGoli i parë i ndeshjes erdhi në minutën e 63-të dhe ishte Jorginho, i cili u tregua i saktë nga pika e bardhë për 1-0.”,
“data”:”2019-10-12 00:00:00″
}
“`