Package net

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:

http://localhost:8082/book/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:

http://localhost:8080/page/1

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″
}
“`

All Rights Reserved Theme by 404 THEME.