Introduction#
GitHub this note shows
- Getting started with Go
- Create a simple web app
- Bedrock streaming response
- Upload file
- Server static file and folder
Install Go#
First, download go from HERE. For Linux, we can use below command to download a version of Go
wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
Second, extract
tar -xvzf go1.21.5.linux-amd64.tar.gz
Next update the PATH environment
echo PATH:PATH_TO_YOUR_GO/go/bin/go:PATH >> ~/.bashrc
Finally, check go version
go version
Hello World#
Let create a new folder called hello
mkdir helloworld
Go into the folder helloworld and init a new go module
go module init hellomodule
Then create a main.go, the project structure look like this
|--hello|--go.mod|--go.sum|--main.go
Content of main.go
package mainimport ("fmt")func main() {fmt.Println("Hello World")}
Run the code
go run main.go
HTTP Server#
Create a http server, server mux and serve a static html file
package mainimport ("fmt""net/http""time")func main () {mux := http.NewServeMux()mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {// fmt.Fprintf(w, "Hello Minh Tran")http.ServeFile(w, r, "index.html")})server := &http.Server{Addr: ":3000",Handler: mux,ReadTimeout: 10 * time.Second,WriteTimeout: 10 * time.Second,MaxHeaderBytes: 1 << 20,}server.ListenAndServe()}
The html file
HTML File
<html><head><style>:root {box-sizing: border-box;}*,::before,::after {box-sizing: inherit;}.container {width: 80%;margin: auto;/* background-color: antiquewhite; */}.question-form {position: relative;}button {cursor: pointer;background-color: orange;padding: 1em;padding-left: 1.5em;padding-right: 1.5em;position: absolute;top: 50%;transform: translateY(-50%);right: 1em;}.text-area {background-color: azure;margin-top: 1em;}.text-input {background-color: aquamarine;width: 100%;padding: 1em;font-size: large;}</style></head><body><div class="container"><div class="question-form"><form><input class="text-input" type="text" id="text-input" /></form><button id="submit">Submit</button></div><div class="text-area"><p id="story-output"></p></div></div><script>const storyOutput = document.getElementById('story-output')const callBedrockStream = async () => {storyOutput.innerText = ''const topic = document.getElementById('text-input').valueif (topic) {try {const response = await fetch('/bedrock-stream', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ topic: topic })})console.log(response)const reader = response.body.getReader()const decoder = new TextDecoder()while (true) {const { done, value } = await reader.read()if (done) {break}const text = decoder.decode(value)console.log(text)storyOutput.innerText += text}} catch (error) {console.log(error)}} else {console.log('Please enter question ...')}}document.getElementById('submit').addEventListener('click', callBedrockStream)</script></body></html>
More simple, we can use the default http server and default mux
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello")})http.ListenAndServe(":3000", nil)
Template and PostgreSQL#
- Read a table in postgresql
- Create a template html
- Pass data to the template
- Responsive frontend
First update the import
import ("bytes""context""encoding/json""fmt""log""net/http""os""text/template""time""github.com/aws/aws-sdk-go-v2/aws""github.com/aws/aws-sdk-go-v2/config""github.com/aws/aws-sdk-go-v2/service/bedrockruntime""github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types""github.com/aws/aws-sdk-go-v2/service/s3""gorm.io/driver/postgres""gorm.io/gorm""gorm.io/gorm/schema")
Create a static serving folder for images link
mux.Handle("/demo/", http.StripPrefix("/demo/", http.FileServer(http.Dir("./static"))))
Let query the table in postresql
type Book struct {ID uintTitle stringAuthor stringAmazon stringImage stringDescription string}func getBooks(db *gorm.DB) []Book {var books []Bookdb.Limit(10).Find(&books)for _, book := range books {fmt.Println(book.Title)}return books}
Let query a list of book and pass to a template
mux.HandleFunc("/postgresql", func(w http.ResponseWriter, r *http.Request) {// query a list of book []Bookbooks := getBooks(db)// load templatetmpl, error := template.ParseFiles("./static/book-template.html")if error != nil {fmt.Println(error)}// pass data to template and write to writertmpl.Execute(w, books)})
The frontend templaet
<html><head><linkhref="https://d2cvlmmg8c0xrp.cloudfront.net/web-css/review-book.css"rel="stylesheet"/></head><body class="bg-gray-100">{{range $book:= .}}<div class="mx-auto max-w-5xl"><div class="md:flex gap-x-5 flex-row mb-8"><div class="ml-4 bg-white flex-auto w-full"><h4 class="font-bold mb-8">{{ $book.Title }}</h4><h4 class="font-bold mb-8">{{ $book.Author }}</h4><imgsrc="/demo/{{ $book.Image }}"alt="book-image"class="float-left h-auto w-64 mr-6"/><p>{{ $book.Description}}</p></div></div></div>{{end}}</body></html>
This is full responsive fontend
responseive html
<html><head><style>.body {background-color: antiquewhite;}.container {max-width: 800px;margin-left: auto;margin-right: auto;}.title {font: bold;margin-bottom: 8px;}.image {float: left;height: auto;width: 128px;margin-right: 6px;}.card {margin-left: 4px;margin-right: 4px;background-color: white;width: 100%;}.grid {display: grid;row-gap: 10px;column-gap: 10px;grid-template-columns: repeat(1, minmax(0, 1fr));}@media (min-width: 35em) {.grid {grid-template-columns: repeat(2, minmax(0, 1fr));}}</style></head><body class="body"><div class="container"><div class="grid">{{range $book:= .}}<div class="card"><h4 class="title">{{ $book.Image}}</h4><h4 class="title">{{ $book.Author }}</h4><img src="/demo/{{ $book.Image }}" alt="book-image" class="image" /><p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Remquaerat quas corrupti cum blanditiis, sint non officiis minusmolestiae culpa consectetur ex voluptatibus distinctio ipsam.Possimus sint voluptatum at modi! Lorem ipsum, dolor sit ametconsectetur adipisicing elit. Alias dolore soluta error adipiscieius pariatur laborum sed impedit. Placeat minus aut perspiciatisdolor veniam, dolores odio sint eveniet? Numquam, tenetur! Loremipsum dolor sit amet consectetur adipisicing elit. Earum suscipitporro animi! Ducimus maiores et non. Minima nostrum ipsa voluptasassumenda consequuntur dicta reprehenderit numquam similique,nesciunt officiis facere optio. {{ $book.Description}}</p></div>{{end}}</div></div></body></html>
Upload File#
- Upload form
- Save file to webserver
- Upload file to S3
- Create a record in database
Let create a simple frontend which is a form for sending post request (multipart data) to server
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>Upload File</title></head><body><form enctype="multipart/form-data" action="/upload" method="post"><input type="file" name="myFile" /><input type="submit" value="upload" /></form></body></html>
Then create a handler at server to save the uploaded file
func uploadFile(w http.ResponseWriter, r *http.Request, db *gorm.DB) {// maximum upload file of 10 MB filesr.ParseMultipartForm(10 << 20)// Get handler for filename, size and heandersfile, handler, error := r.FormFile("myFile")if error != nil {fmt.Println("Error")fmt.Println(error)return}defer file.Close()fmt.Printf("upload file %v\n", handler.Filename)fmt.Printf("file size %v\n", handler.Size)fmt.Printf("MIME header %v\n", handler.Header)// upload file to s3// _, error = s3Client.PutObject(context.TODO(), &s3.PutObjectInput{// Bucket: aws.String("cdk-entest-videos"),// Key: aws.String("golang/" + handler.Filename),// Body: file,// })// if error != nil {// fmt.Println("error upload s3")// }// Create filedest, error := os.Create("./static/" + handler.Filename)if error != nil {return}defer dest.Close()// Copy uploaded file to destif _, err := io.Copy(dest, file); err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}// create a record in databasedb.Create(&Book{Title: "Database Internals",Author: "Hai Tran",Description: "Hello",Image: handler.Filename,})fmt.Fprintf(w, "Successfully Uploaded File\n")}
Bedrock Stream#
Let call bedrock and resopnse stream to client
import ("bytes""context""encoding/json""fmt""io""log""net/http""os""text/template""time""github.com/aws/aws-sdk-go-v2/aws""github.com/aws/aws-sdk-go-v2/config""github.com/aws/aws-sdk-go-v2/service/bedrockruntime""github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types""github.com/aws/aws-sdk-go-v2/service/s3""gorm.io/driver/postgres""gorm.io/gorm""gorm.io/gorm/schema")type Request struct {Prompt string `json:"prompt"`MaxTokensToSample int `json:"max_tokens_to_sample"`Temperature float64 `json:"temperature,omitempty"`TopP float64 `json:"top_p,omitempty"`TopK int `json:"top_k,omitempty"`StopSequences []string `json:"stop_sequences,omitempty"`}type Response struct {Completion string `json:"completion"`}type HelloHandler struct{}type Query struct {Topic string `json:"topic"`}// promt formatconst claudePromptFormat = "\n\nHuman: %s\n\nAssistant:"// bedrock runtime clientvar brc *bedrockruntime.Client// init bedorck credentials connecting to awsfunc init() {region := os.Getenv("AWS_REGION")if region == "" {region = "us-east-1"}cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region))if err != nil {log.Fatal(err)}brc = bedrockruntime.NewFromConfig(cfg)s3Client = s3.NewFromConfig(cfg)}func bedrock(w http.ResponseWriter, r *http.Request) {var query Queryvar message string// parse mesage from requesterror := json.NewDecoder(r.Body).Decode(&query)if error != nil {message = "how to learn japanese as quick as possible?"panic(error)}message = query.Topicfmt.Println(message)prompt := "" + fmt.Sprintf(claudePromptFormat, message)payload := Request{Prompt: prompt,MaxTokensToSample: 2048,}payloadBytes, error := json.Marshal(payload)if error != nil {fmt.Fprintf(w, "ERROR")// return "", error}output, error := brc.InvokeModelWithResponseStream(context.Background(),&bedrockruntime.InvokeModelWithResponseStreamInput{Body: payloadBytes,ModelId: aws.String("anthropic.claude-v2"),ContentType: aws.String("application/json"),},)if error != nil {fmt.Fprintf(w, "ERROR")// return "", error}for event := range output.GetStream().Events() {switch v := event.(type) {case *types.ResponseStreamMemberChunk://fmt.Println("payload", string(v.Value.Bytes))var resp Responseerr := json.NewDecoder(bytes.NewReader(v.Value.Bytes)).Decode(&resp)if err != nil {fmt.Fprintf(w, "ERROR")// return "", err}fmt.Println(resp.Completion)fmt.Fprintf(w, resp.Completion)if f, ok := w.(http.Flusher); ok {f.Flush()} else {fmt.Println("Damn, no flush")}case *types.UnknownUnionMember:fmt.Println("unknown tag:", v.Tag)default:fmt.Println("union is nil or unknown type")}}}
And add a handler to bedrock
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {http.ServeFile(w, r, "./static/bedrock.html")})
The frontend for chatbot using bedrock
<html><head><style>:root {box-sizing: border-box;}*,::before,::after {box-sizing: inherit;}.container {width: 80%;margin: auto;/* background-color: antiquewhite; */}.question-form {position: relative;}button {cursor: pointer;background-color: orange;padding: 1em;padding-left: 1.5em;padding-right: 1.5em;position: absolute;top: 50%;transform: translateY(-50%);right: 1em;}.text-area {background-color: azure;margin-top: 1em;}.text-input {background-color: aquamarine;width: 100%;padding: 1em;font-size: large;}</style></head><body><div class="container"><div class="question-form"><form><input class="text-input" type="text" id="text-input" /></form><button id="submit">Submit</button></div><div class="text-area"><p id="story-output"></p></div></div><script>const storyOutput = document.getElementById('story-output')const callBedrockStream = async () => {storyOutput.innerText = ''const topic = document.getElementById('text-input').valueif (topic) {try {const response = await fetch('/bedrock-stream', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ topic: topic })})console.log(response)const reader = response.body.getReader()const decoder = new TextDecoder()while (true) {const { done, value } = await reader.read()if (done) {break}const text = decoder.decode(value)console.log(text)storyOutput.innerText += text}} catch (error) {console.log(error)}} else {console.log('Please enter question ...')}}document.getElementById('submit').addEventListener('click', callBedrockStream)</script></body></html>
Stream Reseponse#
Let create streaming response using flush
package mainimport ("fmt""net/http""time")type HelloHandler struct {}func (hh HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {for i:= 0; i < 10; i++ {fmt.Fprintf(w, "sending first line of data \n")if f, ok := w.(http.Flusher); ok {f.Flush()} else {fmt.Println("Damn, no flush");}time.Sleep(1000 * time.Millisecond)}}func main() {server := http.Server{Addr: ":3000",ReadTimeout: 30 * time.Second,WriteTimeout: 30 * time.Second,IdleTimeout: 120 * time.Second,Handler: HelloHandler{},}err := server.ListenAndServe()if err != nil {fmt.Println("error")}}
Let create streaming response using echo framework
package mainimport ("encoding/json""net/http""time""github.com/labstack/echo/v4")type (Geolocation struct {Altitude float64Latitude float64Longitude float64})var (locations = []Geolocation{{-97, 37.819929, -122.478255},{1899, 39.096849, -120.032351},{2619, 37.865101, -119.538329},{42, 33.812092, -117.918974},{15, 37.77493, -122.419416},})func main_echo() {e := echo.New()e.GET("/", func(c echo.Context) error {c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)c.Response().WriteHeader(http.StatusOK)enc := json.NewEncoder(c.Response())for _, l := range locations {if err := enc.Encode(l); err != nil {return err}c.Response().Flush()time.Sleep(1 * time.Second)}return nil})e.Logger.Fatal(e.Start(":1323"))}
JavaScript Client#
Let write some javascript code to send request to server and update streaming response text to browser
const storyOutput = document.getElementById('story-output')const callBedrockStream = async () => {storyOutput.innerText = ''const topic = document.getElementById('text-input').valueif (topic) {try {const response = await fetch('/bedrock-stream', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ topic: topic })})console.log(response)const reader = response.body.getReader()const decoder = new TextDecoder()while (true) {const { done, value } = await reader.read()if (done) {break}const text = decoder.decode(value)console.log(text)storyOutput.innerText += text}} catch (error) {console.log(error)}} else {console.log('Please enter question ...')}}document.getElementById('submit').addEventListener('click', callBedrockStream)
Docker#
Stop and remove all container images
docker stop $(docker ps -a -q)docker rmi -f $(docker images -aq)
Delete all existing images
docker rmi -f $(docker images -aq)
Build and tag a new container image
docker build --tag entest .
Stop all process running on port 3000
sudo kill -9 $(lsof -i:3000 -t)
AppRunner#
Let create app runner for hosting the go app. First, let create an autoscaling
import { Stack, StackProps, aws_apprunner, aws_iam } from 'aws-cdk-lib'import { Effect } from 'aws-cdk-lib/aws-iam'import { Construct } from 'constructs'export class AppRunnerStack extends Stack {constructor(scope: Construct, id: string, props: StackProps) {super(scope, id, props)const role = new aws_iam.Role(this, 'RoleForAppRunnerPullEcr', {assumedBy: new aws_iam.ServicePrincipal('build.apprunner.amazonaws.com'),roleName: 'RoleForAppRunnerPullEcr'})role.addToPolicy(new aws_iam.PolicyStatement({effect: Effect.ALLOW,resources: ['*'],actions: ['ecr:*']}))const autoscaling = new aws_apprunner.CfnAutoScalingConfiguration(this,'AutoScalingForGoApp',{autoScalingConfigurationName: 'AutoScalingForGoApp',// min number instanceminSize: 1,// max number instancemaxSize: 10,// max concurrent request per instancemaxConcurrency: 100})const apprunner = new aws_apprunner.CfnService(this, 'GoAppService', {serviceName: 'GoAppServiceDemo',sourceConfiguration: {authenticationConfiguration: {accessRoleArn: role.roleArn},autoDeploymentsEnabled: false,imageRepository: {imageIdentifier:'11122223333444.dkr.ecr.us-east-1.amazonaws.com/go-app:latest',imageRepositoryType: 'ECR',imageConfiguration: {port: '3000',runtimeEnvironmentVariables: [{name: 'BUCKET',value: 'demo'}]// startCommand: "",}}},instanceConfiguration: {cpu: '1 vCPU',memory: '2 GB'},observabilityConfiguration: {observabilityEnabled: false},autoScalingConfigurationArn: autoscaling.ref})apprunner.addDependency(autoscaling)}}
UserData#
UserData can be used when launching a new EC2 instance, so it will install GO and clone the repository for the web app
cd /home/ec2-user/wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gztar -xvf go1.21.5.linux-amd64.tar.gzecho 'PATH=/home/ec2-user/go/bin/go:$PATH' >> ~/.bashrcwget https://github.com/cdk-entest/swinburne-dn-cos20019/archive/refs/heads/main.zipunzip maincd swinburne-dn-cos20019-main/go mod tidygo run main.go