Een krachtige RESTful API bouwen met HTTP-router in Go

In dit artikel gaan we beschrijven hoe je een krachtige en zeer efficiënte REST API-webservice applicatie kan bouwen. Laten we eens beginnen met de onderwerp REST API-webservice in Go. Het standaard http Go-pakket kan zowel de HTTP-client- als servertaken uitvoeren. Het probleem met dit pakket is dat de routering en multiplexing vrij eenvoudig is. Afhankelijk van je behoefte vereist het wat extra codes die kan leiden tot boilerplate codes en dat wil je natuurlijk niet hebben. Om dit te voorkomen, zijn er verschillende andere pakketten die oplossingen bieden. Hier zijn er twee van de meest gebruikte pakketten die je kunt gebruiken:

  • Gorilla/mux maakt deel uit de volledige Gorilla-webtoolkit. Het mux pakket van de toolkit biedt een flexibele set van standaarden waarop elk verzoek kan matchen en dat omvat host, -schemes, HTTP-headers en nog meer.
  • Pat is een pakket die gebaseerd is op het ruby pakket Sinatra. Het doel is om de codes leesbaarder te maken. Daarom bevat de implementatie parameters die makkelijk te lezen zijn.

Voor dit artikel zullen we ons richten op efficiëntie en prestaties van het RESTful API-webservice. Het Julienschmidt http-router pakket is daar meer op gericht om het geheugen verbruik en de korte verwerkingstijd voor routering te minimaliseren. Vandaar dat die beschouwd wordt als een hoog presterende routeringspakket. Dit pakket bevat ook functies zoals hoofdletter gevoelige paden verwerken. Hierbij wordt onnodige en ongeldige tekens in een pad worden verwijderd en optional trailing verwerken (bijv. /.). Daarom zullen we dit pakket ook gebruikt voor onze RESTful api in dit voorbeeld zodat het goed presteren en erg efficiënt gaan draaien.

  • julienschmidt pakket importeren

Zorg er eerst voor dat je GOPATH is goed ingesteld zoals beschreven in deze artikelen:

– Voor MACOS-gebruiker

– Voor Windows-gebruiker

– Voor Linux-user

Open terminal en voer de volgende opdracht uit:

go get github.com/julienschmidt/httprouter

  • Webservice applicatie schrijven

Na het importeren van het pakket kunnen we beginnen met het schrijven van de applicatie broncodes. In dit voorbeeld gaan we een heel eenvoudige webservice applicatie schrijven die eenvoudig JSON server reactie terug sturen. De context van deze webservice applicatie omvat eindpunten voor het ophalen, invoegen, wijzigen en verwijderen van studenten. Deze web applicatie zal een lijst bevatten die dient als een dummy-database aangezien we het zo eenvoudig mogelijk willen houden.

We beginnen met het maken van de benodigde struct (modellen) die nodig zijn voor deze webservice-app.

Student struct

Deze struct bevat de volgende data fields:

ID -> De dummy student ID (van het type int)

Naam -> De naam van het student (van het type string)

Age -> De student leeftijd (van het type int)

type student struct {
ID int `json:"id"`
Naam string `json:"naam"`
Leeftijd int `json:"leeftijd"`
}

Server bericht struct

De server bericht struct wordt gebruikt als een generieke struct voor het terugsturen van berichten naar de client en die bevat slechts één data field, namelijk:

Bericht -> Het server bericht van de server (van het type string)

type serverBericht struct {
	Bericht string `json:"bericht"`
}

Studentenlijst (een dummy weergave van een database)

Deze lijst simuleert een dummy-database voor het invoegen, verwijderen, wijzigen en verwijderen van studenten.

var studentenLijst []student

ID-teller (dummy student ID teller)

Deze teller wordt iedere keer dat een nieuwe student wordt toegevoegd verhoogd. In de context van een database kan dit een student-ID zijn of de primaire sleutel van de database (die automatisch kan worden verhoogd bij het toevoegen van een student). 

var idTeller = 0

Implementeren van belangrijke functies om onze broncode zo schoon mogelijk te behouden

In Golang is het verzenden van server reacties en foutmeldingen een standaard procedure. Om code duplicaties te voorkomen, gaan we enkele cruciale functies implementeren die hergebruikt kunnen worden. Deze functies zijn:

  • Stel als JSON

Deze functie stelt de server reactie koptekst als JSON. Dit is belangrijk voor de http client applicatie zodat die weet dat de server reactie is een JSON.

func stelAlsJson(responseWriter http.ResponseWriter) {
	responseWriter.Header().Set("Content-Type", "application/json")
} 
  • Bad request versturen

Dit is de standaard bad request server reactie die gebruik zullen worden in deze web service applicatie voor het versturen van bad request.

func badRequestVersturen(reactie http.ResponseWriter, bericht string) {

stelAlsJson(reactie)
reactie.WriteHeader(http.StatusBadRequest)
serverFoutBericht := serverBericht{Bericht: bericht}
jsonReactie, jsonFout := json.Marshal(serverFoutBericht)

if jsonFout != nil {
_, _ = reactie.Write([]byte(""))
} else {
_, _ = reactie.Write(jsonReactie)
}
}
  • Interne server fout verzenden

Deze functie is bijna hetzelfde als de vorige, maar in dit geval gaat het om de interne serverfout reactie. Het wordt gebruikt als er iets helemaal mis ging aan de server kant.

func interneServerfoutVerzenden(reactie http.ResponseWriter) {

	stelAlsJson(reactie)
	reactie.WriteHeader(http.StatusInternalServerError)
	serverFoutBericht := serverBericht{Bericht: "Kan verzoek niet verwerken"}
	jsonReactie, jsonFout := json.Marshal(serverFoutBericht)

	if jsonFout != nil {
		_, _ = reactie.Write([]byte(""))
	} else {
		_, _ = reactie.Write(jsonReactie)
	}
}
  • Stuur reactie bericht

Deze functie verwerkt reacties die een specifiek boodschap voor de client bevat. Zo’n boodchap kan een foutmelding zijn of als de server iets wilt informeren aan de client (bijvoorbeeld: de lijst van studenten is leeg etc…).

func stuurReactieBericht(statusCode int, reactie http.ResponseWriter, bericht serverBericht) {

    if statusCode != 0 && reactie != nil {
	stelAlsJson(reactie)
	reactie.WriteHeader(statusCode)
	jsonReactie, jsonFout := json.Marshal(bericht)
	if jsonFout != nil {
	    interneServerfoutVerzenden(reactie)
	} else {
	    _, verzendingFout := reactie.Write(jsonReactie)
	    if verzendingFout != nil {
		interneServerfoutVerzenden(reactie)
	    }
	}
    }
}
  • Reactie verzenden

Deze functie behandelt het verzenden van de server reactie die student gegevens bevatten. In de meeste gevallen zal het gebruikt worden wanneer een verzoek is geslaagd.

func reactieVerzenden(statusCode int, reactie http.ResponseWriter, student student) {

   if statusCode != 0 && reactie != nil {
	stelAlsJson(reactie)
	reactie.WriteHeader(statusCode)
	jsonReactie, jsonFout := json.Marshal(student)
	if jsonFout != nil {
	    interneServerfoutVerzenden(reactie)
	} else {
	    _, verzendingFout := reactie.Write(jsonReactie)
	    if verzendingFout != nil {
		interneServerfoutVerzenden(reactie)
	    }
        }
    }
}
  • Lijst bevat record door id

Deze functie controleert of de studenten lijst (dummy database in ons geval) bevat een specifieke student gebaseerd op de verstrekte ID. Dergelijke ID is gegenereerd door de idTeller variabel bij het invoegen van een student.

func lijstBevatRecordDoorID(id int) bool {

    bevatGegevens := false

    for _, studentGevonden := range studentenLijst {
	if studentGevonden.ID == id {
		bevatGegevens = true
	}
    }

    return bevatGegevens
}
  • Lijst bevat record

Deze functie controleert of de lijst een student record bevat op basis van een opgegeven naam en leeftijd. Dit functie retourneert het twee variabelen, voor meer informatie over dergelijke functies en hoe je met zo’n functie moet omgaan, lees deze artikel.

func lijstBevatRecord(naam string, leeftijd int) (bool, student) {

   bevatGegevens := false
   var student student

   for _, studentGevonden := range studentenLijst {
     if strings.ToLower(studentGevonden.Naam) == strings.ToLower(naam) &&   
        studentGevonden.Leeftijd == leeftijd {
	   bevatGegevens = true
	   student = studentGevonden
      }
   }

   return bevatGegevens, student
}

In dit geval retourneert het een boolean en student struct

Server-eindpunten

In de volgende sectie zullen we de server-eindpunten implementeren en initiëren en koppelen aan de http-router.

  • Router instantie initiëren 

Om het http router pakket te gebruiken, moet je een nieuwe instantie van de router in de hoofdfunctie (main) definiëren.

func main() {
   router := httprouter.New()
}
  • Eindpunt voor het ophalen van alle studenten 

Laten we beginnen met het eindpunt om de lijst van studenten op te halen. Dit eindpunt retourneert JSON array met alle studenten die toegevoegd waren in de lijst. Het pad naar dit eindpunt is ‘/student/all‘ en het is een GET verzoek.

“GET: /student/alle”

Laten we beginnen met het definiëren van onze eerste server-eindpunt aan de router:

func main() {
   router := httprouter.New()
   router.GET("/student/alle", alleStudentenOphalen)
}

Vervolgens moeten we de bijbehorende functie implementeren voor dit eindpunt die verzoeken verwerken.

func alleStudentenOphalen(reactie http.ResponseWriter, _ *http.Request, _ httprouter.Params) {

   stelAlsJson(reactie)
   reactie.WriteHeader(http.StatusOK)

   if len(studentenLijst) == 0 {
	json_resultaat, _ := json.Marshal([]student{})
	_, schrijf_fout := reactie.Write(json_resultaat)
	  if schrijf_fout != nil {
	    interneServerfoutVerzenden(reactie)
	  }
       } else {
	json_resultaat, _ := json.Marshal(studentenLijst)
	  _, schrijf_fout := reactie.Write(json_resultaat)
	if schrijf_fout != nil {
		interneServerfoutVerzenden(reactie)
	}
    }
}
  • Eindpunt voor het ophalen van een student

Met dit eindpunt kun je op basis van het opgegeven ID een specifieke student uit de lijst halen. Het server reactie hierbij zal een json-object zijn met de bijbehorende student gegevens, anders wordt een generieke bericht verstuur die aangeeft dat de student niet bestaat. Het pad voor dit eindpunt is ‘/student‘ en het is een GET verzoek. De student-ID moet worden opgegeven in de verzoek parameter.

“GET: /student”
func studentOphalen(reactie http.ResponseWriter, verzoek *http.Request, _ httprouter.Params) {

	parameter := verzoek.URL.Query()

	if parameter == nil || len(parameter) == 0 || parameter["id"] == nil || parameter["id"][0] == ""      
        {
	   badRequestVersturen(reactie, "ongeldig identiteit")
	   return
	}

	studentID := parameter["id"][0]

	studentIdInt, err := strconv.Atoi(studentID)
	if err != nil {
	   badRequestVersturen(reactie, "ongeldig identiteit")
	   return
	}

	var opgevraagdeStudent = student{
		ID:       -1,
		Naam:     "",
		Leeftijd: -1,
	}

	for _, studentGevonden := range studentenLijst {

	    if studentGevonden.ID == studentIdInt {
		opgevraagdeStudent = studentGevonden
	    }
	}

	if opgevraagdeStudent.ID == -1 {
	    stuurReactieBericht(http.StatusNotFound, reactie, serverBericht{Bericht: "Student niet 
            gevonden"})
	} else {
	    reactieVerzenden(http.StatusOK, reactie, opgevraagdeStudent)
	}
}
  • Eindpunt voor het toevoegen van een student

Dit eindpunt is voor het toevoegen van student in het lijst. Om een student toe te voegen, moeten de vereiste gegevens verstrekt worden in de verzoek. In dit geval de naam en leeftijd. Het server reactie hiervoor is een json-object met de nieuw ingevoegde student gegevens bevatten (inclusief de nieuwe record-ID). Wat betreft het pad voor dit eindpunt, gebruiken we ook ‘/student‘, maar in dit geval gaat het om een POST verzoek.

“POST: /student”
func studentToevoegen(reactie http.ResponseWriter, verzoek *http.Request, _ httprouter.Params) {

   var nieuweStudent student
   verwerkingsFout := json.NewDecoder(verzoek.Body).Decode(&nieuweStudent)

   if verwerkingsFout != nil {
     if verwerkingsFout == io.EOF {
	 badRequestVersturen(reactie, "Onvolledige gegevens")
     } else {
	 interneServerfoutVerzenden(reactie)
     }
     return
  } else if nieuweStudent.Naam == "" || nieuweStudent.Leeftijd == 0 {
     badRequestVersturen(reactie, "Ontbrekende gegevens")
     return
  }

  lijstBevatData, studentGevonden := lijstBevatRecord(nieuweStudent.Naam, nieuweStudent.Leeftijd)

  if lijstBevatData {
      reactieVerzenden(http.StatusOK, reactie, studentGevonden)
  } else {
      idTeller++

      studentToevoegen := student{
	 ID: idTeller,
	 Naam: nieuweStudent.Naam,
	 Leeftijd: nieuweStudent.Leeftijd,
      }
      studentenLijst = append(studentenLijst, studentToevoegen)
      reactieVerzenden(http.StatusOK, reactie, studentToevoegen)
   }
}
  • Eindpunt voor het verwijderen van een student

Dit eindpunt is voor het verwijderen van studenten uit de lijst. In dit geval moet alleen de student-ID worden opgegeven als onderdeel van de verzoek parameter. Het pad voor dit eindpunt is ‘/student‘ en het is een DELETE verzoek.

“DELETE: /student”
func studentVerwijderen(reactie http.ResponseWriter, verzoek *http.Request, _ httprouter.Params) {

   parameter := verzoek.URL.Query()

   if parameter == nil || len(parameter) == 0 || parameter["id"] == nil || parameter["id"][0] == ""
   {
	badRequestVersturen(reactie, "ongeldig identiteit")
	return
   }

   studentID := parameter["id"][0]

   studentIdInt, err := strconv.Atoi(studentID)
   if err != nil {
	badRequestVersturen(reactie, "ongeldig identiteit")
	return
   }

   for index, studentFound := range studentenLijst {
	if studentFound.ID == studentIdInt {
		studentenLijst = append(studentenLijst[:index], studentenLijst[index+1:]...)
	}
   }
   stuurReactieBericht(http.StatusOK, reactie, serverBericht{Bericht: "Student verwijderd"})
}
  • Eindpunt voor het bijwerken van studenten

Ten slotte, de eindpunt voor het bijwerken van student gegevens. In dit geval moet de student-ID worden opgegeven als onderdeel van de verzoek parameter. De nieuw bijgewerkte gegevens zal deel uitmaken van de verzoek body. De pad voor dit eindpunt is ‘/student/update‘ en het is van het type PATCH.

“PATCH: /student/update”
func updateStudent(reactie http.ResponseWriter, verzoek *http.Request, _ httprouter.Params) {

   parameter := verzoek.URL.Query()

   if parameter == nil || len(parameter) == 0 || parameter["id"] == nil || parameter["id"][0] == "" {
	badRequestVersturen(reactie, "ongeldig identiteit")
	return
   }

   studentID := parameter["id"][0]

   studentIdInt, err := strconv.Atoi(studentID)
   if err != nil {
	badRequestVersturen(reactie, "ongeldig identiteit")
	return
   }

   lijstBevatData := lijstBevatRecordDoorID(studentIdInt)

   var geupdateStudent student
   parseError := json.NewDecoder(verzoek.Body).Decode(&geupdateStudent)

   if !lijstBevatData {
	stuurReactieBericht(http.StatusNotFound, reactie, serverBericht{Bericht: "Student niet 
        gevonden"})
	return
   }

   if parseError != nil {
      if parseError == io.EOF {
	   badRequestVersturen(reactie, "Onvolledige gegevens")
      } else {
	   interneServerfoutVerzenden(reactie)
      }
      return
   }

   for index, studentGevonden := range studentenLijst {

      if studentGevonden.ID == studentIdInt {

	 studentOmTeUpdaten := &studentenLijst[index]

	 if studentOmTeUpdaten.Naam != "" {
	     studentOmTeUpdaten.Naam = geupdateStudent.Naam
	 }

	 if studentOmTeUpdaten.Leeftijd != 0 {
	     studentOmTeUpdaten.Leeftijd = geupdateStudent.Leeftijd
	 }

	geupdateStudent = *studentOmTeUpdaten
      }
   }

  reactieVerzenden(http.StatusOK, reactie, geupdateStudent)
}
  • Hoofdfunctie (main) en router instantie afmaken 

Nadat alle eindpunten zijn geïmplementeerd, moeten ze gekoppeld worden aan de router zodat je kunt beginnen met hosten en luisteren naar inkomende verzoeken van de client. Dat kan gedaan worden door de resterende eindpunten aan de router te koppelen in de hoofdfunctie zoals geïllustreerd is hieronder:

func main() {

   router := httprouter.New()
   router.GET("/student/alle", alleStudentenOphalen)
   router.GET("/student", studentOphalen)
   router.PATCH("/student/update", updateStudent)
   router.POST("/student", studentToevoegen)
   router.DELETE("/student", studentVerwijderen)

   serverFout := http.ListenAndServe(":3000", router)
   if serverFout != nil {
      log.Fatal("Kan webserver niet starten, oorzaak: ", serverFout)
   }
}

Webservice app opstarten en testen

Om de webservice-applicatie te testen, gaan we postman gebruiken om verzoeken te versturen en het server-reactie weergeven. Als je postman nog niet hebt geïnstalleerd, download deze dan van hun officiële site.

Om de server te starten, moet je de GOPATH instellen. Als je meer informatie wilt over hoe je de GOPATH kan instellen, lees de volgende artikelen:

– Voor MACOS-gebruiker

– Voor Windows-gebruiker

– Voor Linux-user

Start de server op door te navigeren naar het project bestand en voert de volgende opdracht uit:

go run efficient_hoogwaardige_webservice.go

In dit voorbeeld is onze go-bestandsnaam efficient_hoogwaardige_webservice.go.

  • Student toevoegen
  • Student opvragen
  • Alle studenten ophalen
  • Student gegevens bijwerken
  • Student verwijderen

Zie onze GitHub repository voor de broncode van het project en kloon onze repository voor alle toekomstige projecten.

Volg ons:

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *