diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..f73a204 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,45 @@ +name: Go CI + +on: + push: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.23.1' + - name: Build + run: go build -v ./... + + + golangci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.60 + - name: Go Format + uses: Jerome1337/gofmt-action@v1.0.5 + with: + gofmt-path: './src' + gofmt-flags: '-l -d' + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: Test http server + run: go test -v ./pkg/httpserver + - name: Test gin server + run: go test -v ./pkg/ginserver diff --git a/Dockerfile.gin b/Dockerfile.gin new file mode 100644 index 0000000..39f42e2 --- /dev/null +++ b/Dockerfile.gin @@ -0,0 +1,17 @@ +FROM golang:latest AS builder + +WORKDIR /build + +COPY . . + +RUN go mod download + +RUN CGO_ENABLED=0 GOOS=linux go build -o ginout ./cmd/ginserver/main.go + +EXPOSE 8083 + +FROM scratch +WORKDIR /app +COPY --from=builder /build/ginout . + +ENTRYPOINT [ "/app/ginout" ] diff --git a/Dockerfile.http b/Dockerfile.http new file mode 100644 index 0000000..337a84b --- /dev/null +++ b/Dockerfile.http @@ -0,0 +1,17 @@ +FROM golang:latest AS builder + +WORKDIR /build + +COPY . . + +RUN go mod download + +RUN CGO_ENABLED=0 GOOS=linux go build -o httpout ./cmd/httpserver/main.go + +EXPOSE 8080 + +FROM scratch +WORKDIR /app +COPY --from=builder /build/httpout . + +ENTRYPOINT [ "/app/httpout" ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f5d1986 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +build: + go build -o httpout ./cmd/httpserver/main.go + go build -o ginout ./cmd/ginserver/main.go + +format: + go fmt ./... + +lint: + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 + golangci-lint run ./... + +build-run-Images: + docker-compose up + +all: build format lint build-run-Images diff --git a/README.md b/README.md index 6191c0c..2bec510 100644 --- a/README.md +++ b/README.md @@ -1 +1,55 @@ -# Date-time-server-RawanMostafa +# Datetime Server + +This repository implements http and Gin datetime servers. + +## Table of Contents + +- [Installation](#installation) +- [Usage](#usage) + + +## Installation + +1. Clone the repository + + ```bash + git clone https://github.com/codescalersinternships/Datetime-server-RawanMostafa.git + ``` + +2. Install the dependencies + ```bash + go mod download + ``` + +## Usage + +### 1. Using Makefile + + To run all make targets + + ```bash + make all + ``` + +### 2. Using docker-compose + + + ```bash + docker-compose up + ``` + +### 2. Using kubernetes deployed server + + + ```bash + curl http://185.206.122.17:30100/ + ``` + +### 3. Using main.go + + ```bash + go run cmd/httpserver/main.go + ``` + ```bash + go run cmd/ginserver/main.go + ``` diff --git a/cmd/ginserver/main.go b/cmd/ginserver/main.go new file mode 100644 index 0000000..55956b3 --- /dev/null +++ b/cmd/ginserver/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + ginhandler "github.com/codescalersinternships/Datetime-server-RawanMostafa/pkg/ginserver" + + "github.com/gin-gonic/gin" +) + +const defaultPort = "8083" + +func getFlags() string { + var port string + flag.StringVar(&port, "port", "", "Specifies the port") + + flag.Parse() + return port +} + +func decideConfigs() string { + + port := getFlags() + + if port == "" { + envPort, found := os.LookupEnv("DATETIME_PORT") + + if found { + port = envPort + } else { + port = defaultPort + } + } + return port + +} +func main() { + port := decideConfigs() + r := gin.Default() + r.GET("/", ginhandler.GinHome) + r.GET("/datetime", ginhandler.GinHandler) + err := r.Run(fmt.Sprintf(":%s", port)) + if err != nil { + log.Fatalf("Error: impossible to start server: %s", err) + } +} diff --git a/cmd/httpserver/main.go b/cmd/httpserver/main.go new file mode 100644 index 0000000..90372d3 --- /dev/null +++ b/cmd/httpserver/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + + httphandler "github.com/codescalersinternships/Datetime-server-RawanMostafa/pkg/httpserver" +) + +const defaultPort = "8083" + +func getFlags() string { + var port string + flag.StringVar(&port, "port", "", "Specifies the port") + + flag.Parse() + return port +} + +func decideConfigs() string { + + port := getFlags() + + if port == "" { + envPort, found := os.LookupEnv("DATETIME_PORT") + + if found { + port = envPort + } else { + port = defaultPort + } + } + return port + +} +func main() { + port := decideConfigs() + + fmt.Println("Starting our simple http server.") + + http.HandleFunc("/", httphandler.HttpHome) + http.HandleFunc("/datetime", httphandler.HttpHandler) + + fmt.Printf("Started on port :%s\n", port) + + err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil) + if err != nil { + log.Fatal(err) + } +} diff --git a/datetime-server/.helmignore b/datetime-server/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/datetime-server/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/datetime-server/Chart.yaml b/datetime-server/Chart.yaml new file mode 100644 index 0000000..0a57ddb --- /dev/null +++ b/datetime-server/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: datetime-server +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/datetime-server/templates/gin-deployment.yml b/datetime-server/templates/gin-deployment.yml new file mode 100644 index 0000000..95dc0ec --- /dev/null +++ b/datetime-server/templates/gin-deployment.yml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gin-deployment + labels: + app: {{ .Values.ginAppName }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.ginAppName }} + template: + metadata: + labels: + app: {{ .Values.ginAppName }} + spec: + containers: + - name: {{ .Values.ginAppName }} + image: "{{ .Values.ginImage.name }}:{{ .Values.ginImage.tag }}" + ports: + - containerPort: 8083 diff --git a/datetime-server/templates/gin-service.yml b/datetime-server/templates/gin-service.yml new file mode 100644 index 0000000..40b043b --- /dev/null +++ b/datetime-server/templates/gin-service.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: gin-service +spec: + type: NodePort + selector: + app: {{ .Values.ginAppName }} + ports: + - protocol: TCP + port: 80 + targetPort: 8083 + nodePort: 30500 \ No newline at end of file diff --git a/datetime-server/templates/http-deployment.yml b/datetime-server/templates/http-deployment.yml new file mode 100644 index 0000000..250ec57 --- /dev/null +++ b/datetime-server/templates/http-deployment.yml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: http-deployment + labels: + app: {{ .Values.httpAppName }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.httpAppName }} + template: + metadata: + labels: + app: {{ .Values.httpAppName }} + spec: + containers: + - name: {{ .Values.httpAppName }} + image: "{{ .Values.httpImage.name }}:{{ .Values.httpImage.tag }}" + ports: + - containerPort: 8080 + diff --git a/datetime-server/templates/http-service.yml b/datetime-server/templates/http-service.yml new file mode 100644 index 0000000..8d5cd54 --- /dev/null +++ b/datetime-server/templates/http-service.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: http-service +spec: + type: NodePort + selector: + app: {{ .Values.httpAppName }} + ports: + - protocol: TCP + port: 8083 + targetPort: 8080 + nodePort: 30400 \ No newline at end of file diff --git a/datetime-server/values.yaml b/datetime-server/values.yaml new file mode 100644 index 0000000..287f894 --- /dev/null +++ b/datetime-server/values.yaml @@ -0,0 +1,10 @@ +ginAppName: gin-server +httpAppName: http-server + +ginImage: + name: rawanmostafa/ginserver + tag: latest + +httpImage: + name: rawanmostafa/httpserver + tag: latest \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a4db306 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3.7' +services: + app1: + build: + context: . + dockerfile: Dockerfile.http + ports: + - "8080:8080" + + app2: + build: + context: . + dockerfile: Dockerfile.gin + ports: + - "8083:8083" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9269c9c --- /dev/null +++ b/go.mod @@ -0,0 +1,35 @@ +module github.com/codescalersinternships/Datetime-server-RawanMostafa + +go 1.23 + +require ( + github.com/bytedance/sonic v1.12.2 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.10.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.10.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/tools v0.25.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bd059c3 --- /dev/null +++ b/go.sum @@ -0,0 +1,92 @@ +github.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg= +github.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= +golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/ginserver/ginhandler.go b/pkg/ginserver/ginhandler.go new file mode 100644 index 0000000..6faa3f7 --- /dev/null +++ b/pkg/ginserver/ginhandler.go @@ -0,0 +1,33 @@ +package ginhandler + +import ( + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +func GinHandler(c *gin.Context) { + if c.Request.Method != "GET" { + c.String(http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed)) + return + } + formattedTime := time.Now().Format(time.ANSIC) + + if strings.Contains(c.Request.Header.Get("content-type"), "text/plain") { + c.Writer.Header().Set("Content-Type", "text/plain") + c.String(http.StatusOK, formattedTime) + + } else if strings.Contains(c.Request.Header.Get("content-type"), "application/json") { + + c.JSON(http.StatusOK, formattedTime) + } else { + c.String(http.StatusUnsupportedMediaType, http.StatusText(http.StatusUnsupportedMediaType)) + } + +} + +func GinHome(c *gin.Context) { + c.String(http.StatusOK, "Welcome to my datetime server!") +} diff --git a/pkg/ginserver/ginhandler_test.go b/pkg/ginserver/ginhandler_test.go new file mode 100644 index 0000000..f192063 --- /dev/null +++ b/pkg/ginserver/ginhandler_test.go @@ -0,0 +1,125 @@ +package ginhandler + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "reflect" + "testing" + "time" + + "github.com/gin-gonic/gin" +) + +func assertEquality(t *testing.T, obj1 any, obj2 any) { + t.Helper() + if reflect.TypeOf(obj1) != reflect.TypeOf(obj2) { + t.Errorf("Error! type mismatch, wanted: %t got: %t", reflect.TypeOf(obj1), reflect.TypeOf(obj2)) + } + if !reflect.DeepEqual(obj1, obj2) { + t.Errorf("Error! values mismatch, wanted: %v got: %v", obj1, obj2) + } +} + +func TestGinHome(t *testing.T) { + + r := gin.Default() + r.GET("/", GinHome) + + req, _ := http.NewRequest("GET", "/", nil) + res := httptest.NewRecorder() + r.ServeHTTP(res, req) + + expected := "Welcome to my datetime server!" + resBody, err := io.ReadAll(res.Body) + if err != nil { + t.Errorf("Error reading response body %v", err) + } + assertEquality(t, expected, string(resBody)) + assertEquality(t, 200, res.Code) +} + +func TestGinHandler(t *testing.T) { + + formattedTime := time.Now().Format(time.ANSIC) + timeJson, err := json.Marshal(formattedTime) + if err != nil { + t.Errorf("error converting to json: %v", err) + } + testcases := []struct { + testcaseName string + method string + url string + statusCode int + expected any + contentType string + }{ + { + testcaseName: "correct method and url, plain text type", + method: "GET", + url: "/datetime", + statusCode: 200, + expected: formattedTime, + contentType: "text/plain", + }, + { + testcaseName: "correct method and url, json type", + method: "GET", + url: "/datetime", + statusCode: 200, + expected: timeJson, + contentType: "application/json", + }, + { + testcaseName: "unsupported content type", + method: "GET", + url: "/datetime", + statusCode: http.StatusUnsupportedMediaType, + expected: http.StatusText(http.StatusUnsupportedMediaType), + contentType: "text/javascript; charset=utf-8", + }, + { + testcaseName: "wrong method", + method: "POST", + url: "/datetime", + statusCode: 405, + expected: http.StatusText(http.StatusMethodNotAllowed), + }, + } + + for _, testcase := range testcases { + t.Run(testcase.testcaseName, func(t *testing.T) { + + r := gin.Default() + + if testcase.method == "GET" { + r.GET(testcase.url, GinHandler) + } else { + r.POST(testcase.url, GinHandler) + } + + req, err := http.NewRequest(testcase.method, testcase.url, nil) + req.Header.Add("content-type", testcase.contentType) + + if err != nil { + t.Errorf("Error in new request %v", err) + } + res := httptest.NewRecorder() + r.ServeHTTP(res, req) + + resBody, err := io.ReadAll(res.Body) + if err != nil { + t.Errorf("Error reading response body %v", err) + } + if testcase.contentType == "application/json" { + assertEquality(t, testcase.expected, resBody) + } else { + assertEquality(t, testcase.expected, string(resBody)) + } + assertEquality(t, testcase.statusCode, res.Code) + + }) + } + +} diff --git a/pkg/httpserver/httphandler.go b/pkg/httpserver/httphandler.go new file mode 100644 index 0000000..741a273 --- /dev/null +++ b/pkg/httpserver/httphandler.go @@ -0,0 +1,44 @@ +package httphandler + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strings" + "time" +) + +func HttpHome(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Welcome to my datetime server!") +} + +func HttpHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return + } + formattedTime := time.Now().Format(time.ANSIC) + + if strings.Contains(r.Header.Get("content-type"), "text/plain") { + + w.Header().Set("Content-Type", "text/plain") + fmt.Fprint(w, formattedTime) + + } else if strings.Contains(r.Header.Get("content-type"), "application/json") { + + w.Header().Set("Content-Type", "application/json") + + timeJson, err := json.Marshal(formattedTime) + if err != nil { + log.Fatalf("error converting to json: %v", err) + } + _, err = w.Write(timeJson) + if err != nil { + log.Fatalf("error writing data to response: %v", err) + } + } else { + http.Error(w, http.StatusText(http.StatusUnsupportedMediaType), http.StatusUnsupportedMediaType) + } + +} diff --git a/pkg/httpserver/httphandler_test.go b/pkg/httpserver/httphandler_test.go new file mode 100644 index 0000000..4615251 --- /dev/null +++ b/pkg/httpserver/httphandler_test.go @@ -0,0 +1,110 @@ +package httphandler + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "reflect" + "testing" + "time" +) + +func assertEquality(t *testing.T, obj1 any, obj2 any) { + t.Helper() + if reflect.TypeOf(obj1) != reflect.TypeOf(obj2) { + t.Errorf("Error! type mismatch, wanted: %t got: %t", reflect.TypeOf(obj1), reflect.TypeOf(obj2)) + } + if !reflect.DeepEqual(obj1, obj2) { + t.Errorf("Error! values mismatch, wanted: %v got: %v", obj1, obj2) + } +} + +func TestHttpHome(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + + w := httptest.NewRecorder() + HttpHome(w, req) + resp := w.Result() + resBody, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading response body %v", err) + } + + expected := "Welcome to my datetime server!" + + assertEquality(t, expected, string(resBody)) + assertEquality(t, 200, resp.StatusCode) +} + +func TestHttpHandler(t *testing.T) { + formattedTime := time.Now().Format(time.ANSIC) + timeJson, err := json.Marshal(formattedTime) + if err != nil { + t.Errorf("error converting to json: %v", err) + } + testcases := []struct { + testcaseName string + method string + url string + statusCode int + expected any + contentType string + }{ + { + testcaseName: "correct method and url, plain text type", + method: "GET", + url: "/datetime", + statusCode: http.StatusOK, + expected: formattedTime, + contentType: "text/plain", + }, + { + testcaseName: "correct method and url, json type", + method: "GET", + url: "/datetime", + statusCode: http.StatusOK, + expected: timeJson, + contentType: "application/json", + }, + { + testcaseName: "unsupported content type", + method: "GET", + url: "/datetime", + statusCode: http.StatusUnsupportedMediaType, + expected: http.StatusText(http.StatusUnsupportedMediaType) + "\n", + contentType: "text/javascript; charset=utf-8", + }, + { + testcaseName: "wrong method", + method: "POST", + url: "/datetime", + statusCode: http.StatusMethodNotAllowed, + expected: http.StatusText(http.StatusMethodNotAllowed) + "\n", + }, + } + + for _, testcase := range testcases { + t.Run(testcase.testcaseName, func(t *testing.T) { + + req := httptest.NewRequest(testcase.method, testcase.url, nil) + req.Header.Add("content-type", testcase.contentType) + + w := httptest.NewRecorder() + HttpHandler(w, req) + resp := w.Result() + resBody, err := io.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading response body %v", err) + } + if testcase.contentType == "application/json" { + assertEquality(t, testcase.expected, resBody) + } else { + assertEquality(t, testcase.expected, string(resBody)) + } + assertEquality(t, testcase.statusCode, resp.StatusCode) + + }) + } + +}