gofiber / contrib Goto Github PK
View Code? Open in Web Editor NEW๐งฌ Repository for third party middlewares with dependencies
Home Page: https://docs.gofiber.io/contrib/
License: MIT License
๐งฌ Repository for third party middlewares with dependencies
Home Page: https://docs.gofiber.io/contrib/
License: MIT License
In fiberzaplog, fiberzerolog middleware, the configuration item SkipURIs
only supports equal to comparison. In fact, many routes have parameters, for URL with routing parameters, cannot be effectively excluded by SkipURIs
.
All, SkipURIs
need to be set to a regular expression or other routing matching pattern to effectively solve this problem.
I hope this feature will be realized soon.
thanks.
htmx allows backend devs to build html based GUI using https://github.com/gofiber/template and then updating the html i the browser.
The core concept with HTMX is that you update the GUI from the backend sending html that then replaces a target html DOM. Its highly productive and fast. Then you do not need React, Vue and all those complex gui libs.
You can see running examples here with source code: https://htmx.org/examples/
Here are a few golang examples:
https://github.com/donseba/go-htmx
https://github.com/livefir/fir
All you need is to include one js in your html page.
htmx (and hyperscript, alpinejs) too.
It's recommended to set tracer name to the package name. However, currently, tracer name is go.opentelemetry.io/contrib/instrumentation/github.com/gofiber/fiber/otelfiber
.
Perhaps, it should be github.com/gofiber/contrib/otelfiber
๐ค
Hi team!
I have an issue. When there are many concurrent requests to our system, fiberi18n throws error like this
[2023-05-16T04:55:32.634958995Z] 57398fb9-da68-4ea2-b008-51da27c5a0ab:: 200 - GET /threads 7.703849ms -โ
fatal error: concurrent map writes
fatal error: concurrent map writes
goroutine 172 [running]:
github.com/gofiber/contrib/fiberi18n.New.func1(0xc0002282c0)
/go/pkg/mod/github.com/gofiber/contrib/[email protected]/i18n.go:42 +0x209
github.com/gofiber/fiber/v2.(*App).next(0xc0001b8480, 0xc0002282c0)
/go/pkg/mod/github.com/gofiber/fiber/[email protected]/router.go:134 +0x1b6
github.com/gofiber/fiber/v2.(*App).handler(0xc0001b8480, 0x4cc317?)
/go/pkg/mod/github.com/gofiber/fiber/[email protected]/router.go:160 +0x87
github.com/valyala/fasthttp.(*Server).serveConn(0xc00054c200, {0xf7e6f8?, 0xc0000aa1b0})
/go/pkg/mod/github.com/valyala/[email protected]/server.go:2371 +0x11d3
github.com/valyala/fasthttp.(*workerPool).workerFunc(0xc000539720, 0xc0000c0b60)
/go/pkg/mod/github.com/valyala/[email protected]/workerpool.go:224 +0xa9
github.com/valyala/fasthttp.(*workerPool).getCh.func1()
/go/pkg/mod/github.com/valyala/[email protected]/workerpool.go:196 +0x38
created by github.com/valyala/fasthttp.(*workerPool).getCh
/go/pkg/mod/github.com/valyala/[email protected]/workerpool.go:195 +0x1b0
goroutine 1 [IO wait, 1 minutes]:
internal/poll.runtime_pollWait(0x7fb9247119b8, 0x72)
/usr/local/go/src/runtime/netpoll.go:306 +0x89
internal/poll.(*pollDesc).wait(0xc0004ad100?, 0x4?, 0x0)
/usr/local/go/src/internal/poll/fd_poll_runtime.go:84 +0x32
internal/poll.(*pollDesc).waitRead(...)
/usr/local/go/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Accept(0xc0004ad100)
/usr/local/go/src/internal/poll/fd_unix.go:614 +0x2bd
net.(*netFD).accept(0xc0004ad100)
/usr/local/go/src/net/fd_unix.go:172 +0x35
net.(*TCPListener).accept(0xc0004c5950)
/usr/local/go/src/net/tcpsock_posix.go:148 +0x25
net.(*TCPListener).Accept(0xc0004c5950)
/usr/local/go/src/net/tcpsock.go:297 +0x3d
github.com/valyala/fasthttp.acceptConn(0xc00054c200, {0xf7a710, 0xc0004c5950}, 0xc0006938b8)
/go/pkg/mod/github.com/valyala/[email protected]/server.go:1930 +0x62
github.com/valyala/fasthttp.(*Server).Serve(0xc00054c200, {0xf7a710?, 0xc0004c5950})
/go/pkg/mod/github.com/valyala/[email protected]/server.go:1823 +0x4f4
github.com/gofiber/fiber/v2.(*App).Listen(0xc0001b8480, {0xe3e28a?, 0xd52f40?})
/go/pkg/mod/github.com/gofiber/fiber/[email protected]/listen.go:82 +0x110
main.main()
/build/cmd/api/api.go:119 +0x357
My code implemented:
app.Use(
fiberi18n.New(&fiberi18n.Config{
RootPath: "./localize",
AcceptLanguages: []language.Tag{language.Vietnamese, language.English},
DefaultLanguage: language.English,
}),
)
msg := fiberi18n.MustGetMessage("successfully")
Please help me to solve this problem.
I notice that the Locals
are read-only within the Websocket, and I was wonder if there is a reason for that?
I'm looking to persist user context through-out the length of the connection and was hoping to use Locals
.
Would something like the below change be feasible? (I can submit a PR if so)
// Locals makes it possible to pass interface{} values under string keys scoped to the request
// and therefore available to all following routes that match the request.
func (conn *Conn) Locals(key string, value ...interface{}) interface{} {
if len(value) == 0 {
return conn.locals[key]
}
conn.locals[key] = value[0]
return value[0]
}
Currently, Casbin supports multiple types of Enforcer
:
Enforcer
SyncedEnforcer
CachedEnforcer
SyncedCachedEnforcer
DistributedEnforcer
This doesn't deviate from the core functionalities of the Enforcer
, which is described in the interface IEnforcer
. The types of enforcers acts as a decorator to the actual Enforcer. As such, users should be able to use the middleware with other types of Enforcer.
No response
package main
import (
c "github.com/casbin/casbin/v2"
"github.com/gofiber/contrib/casbin"
"github.com/gofiber/fiber/v2"
)
func main() {
enforcer, _ := c.NewEnforcer("path/to/basic_model.conf", "path/to/basic_policy.csv")
//enforcer, _ := c.NewSyncedEnforcer("path/to/basic_model.conf", "path/to/basic_policy.csv") // This fails, but it should work.
return casbin.New(casbin.Config{
Enforcer: enforcer,
Lookup: func(c *fiber.Ctx) string {
claims, ok := c.UserContext().Value(model.AuthContextKey).(*model.AuthClaims)
if !ok {
logger.Error(nil, "failed to get auth claims")
return ""
}
return claims.Role
},
Unauthorized: func(c *fiber.Ctx) error {
return model.Response(c, http.StatusUnauthorized)
},
Forbidden: func(c *fiber.Ctx) error {
return model.Response(c, http.StatusForbidden)
},
})
}
Run the code snippet, and the logging is as follows
{"level":"info","ts":1695283707.964828,"caller":"backend2/main.go:23","msg":">>>"}
{"level":"info","ts":1695283707.965184,"caller":"[email protected]/router.go:145","msg":"ready"}
{"level":"info","ts":1695283707.965214,"caller":"backend2/main.go:25","msg":"<<<"}
caller is [email protected]/router.go:145
, but it is backend2/main.go:24
actually
package main
import (
"github.com/gofiber/contrib/fiberzap/v2"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
//setup.Init()
app := fiber.New()
log.SetLogger(fiberzap.NewLogger(fiberzap.LoggerConfig{
ZapOptions: []zap.Option{
zap.AddCaller(),
zap.AddCallerSkip(3),
zap.AddStacktrace(zapcore.PanicLevel),
},
}))
app.Get("/ready", func(c *fiber.Ctx) error {
log.Info(">>>")
log.WithContext(c.UserContext()).Info("ready")
log.Info("<<<")
return c.SendString("ready")
})
err := app.Listen(":3000")
if err != nil {
log.Error(err)
return
}
}
go version 1.20.6
Backend service with some endpoints without database. Three endpoints with working with http
protocol and one with websoket
.
If look at scheen in the end of the terminal cls.Rooms
changed client id
from test
to m_te
. I can't understand this behavior, because in all of the endpoints I don't have any domain logic that can change in cls.Rooms
ClientID
in Room
struct. After some of time I realized that m_te
is peace of RoomID
. I don't have any idea how peace of RoomID
that key in map
replaced client id
.
Steps to reproduce the behavior:
POST
request byregister
url endpoint and didn't pass any input
values in body.GET
request by /rooms/:user_uuid
url.POST
request to create room struct
in map
and also didn't pass any values in http
body.ws
connection.Data in Clients.Rooms
map
by key room_test
after connection by ws
client id
should not changed.
v2.49.0, ws - v2.2.1
package main
import (
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
)
type Clients struct {
Clients map[string]*Client
Rooms map[string]ClientRoom
}
type ClientRoom struct {
RoomID string `json:"roomId"`
Name string `json:"name"`
ClientID string `json:"clientId"`
}
type Room struct {
ID string
Name string
JoinClients map[string]string
}
type Client struct {
ID string
Rooms map[string]*Room
Username string
}
func NewClients() *Clients {
cls := &Clients{
Clients: make(map[string]*Client),
Rooms: make(map[string]ClientRoom),
}
return cls
}
func NewClient(username, uuid string) *Client {
c := &Client{
ID: uuid,
Username: username,
Rooms: make(map[string]*Room),
}
return c
}
func main(){
app := fiber.New()
cls := NewClients()
//Register
app.Post("/register", func(c *fiber.Ctx) error {
id := "test"
username := "username"
client := NewClient(username, id)
cls.Clients[id] = client
fmt.Println("REGISTER", cls.Clients)
fmt.Println("REGISTER", cls.Rooms)
return c.Status(200).JSON(&fiber.Map{
"success": true,
"clientUUID": client.ID,
"username": username,
})
})
//Just for create room
app.Post("/rooms/:user_uuid", func(c *fiber.Ctx) error {
uuid := c.Params("user_uuid")
cl := cls.Clients[uuid]
id := "room_test"
roomName := "room"
r := &Room{
ID: id,
Name: roomName,
JoinClients: make(map[string]string),
}
cl.Rooms[id] = r
cls.Rooms[id] = ClientRoom{ClientID: uuid, RoomID: id, Name: roomName}
fmt.Println("CREATE_ROOM", cls.Clients)
fmt.Println("CREATE_ROOM", cls.Rooms)
return c.Status(200).JSON(&fiber.Map{
"success": true,
"roomUUID": id,
"roomName": roomName,
})
})
//Get all rooms
app.Get("/rooms/:user_uuid", func(c *fiber.Ctx) error {
fmt.Println("GET_ROOMS", cls.Clients)
fmt.Println("GET_ROOMS", cls.Rooms)
return c.Status(200).JSON(&fiber.Map{
"success": true,
"rooms": []string{},
})
})
//Websocket
app.Get("/ws/:roomId", websocket.New(func(c *websocket.Conn) {
defer func() {
c.Close()
}()
fmt.Println("WS", cls.Clients)
fmt.Println("WS", cls.Rooms)
}))
log.Fatal(app.Listen(":3001"))
}
Hi everyone,
Debugging websocket connection errors can be quite difficult in some scenarios and I was wondering whether it would be possible to expose the underlying Upgrade
error.
I may be wrong but it does not seem to be possible to retrieve the error returned by the FastHTTP Upgrade
function as the middleware always returns a ErrUpgradeRequired
error.
https://github.com/gofiber/contrib/blob/main/websocket/websocket.go#L132
The idea is to add a new struct
to wrap the original error
type UpgradeError struct {
reason error
}
func (e *UpgradeError) Error() string {
return fiber.ErrUpgradeRequired.Error()
}
func (e *UpgradeError) Unwrap() error {
return e.reason
}
// ...
if err := upgrader.Upgrade(c.Context(), func(fconn *websocket.Conn) {
conn.Conn = fconn
defer releaseConn(conn)
handler(conn)
}); err != nil { // Upgrading required
return &UpgradeError{reason: err}
}
Users can then wrap the middleware with their own error handler
handleErrors(websocket.New(func(c *websocket.Conn) {})
func handleErrors(handler fiber.Handler) fiber.Handler {
return func(c *fiber.Ctx) error {
err := handler(c)
var upgradeError *UpgradeError
if errors.As(err, &upgradeError ) {
lg.Println(upgradeError.Unwrap())
}
return err
}
}
Hopefully my explanation makes sense. I'm happy to submit a pull request if you find this useful.
Thanks for the great work on fiber
!
No response
package main
import "github.com/gofiber/contrib/%package%"
func main() {
// Steps to reproduce
}
In https://github.com/gofiber/contrib/tree/main/paseto , I saw following is used
go get -u github.com/o1egl/paseto
but that only support paseto v1 and v2, refer: https://paseto.io/
Paseto v3 and v4 is preferred to use now.
So, you may need to support v4, probably via one of:
Testing a function that provide swagger.Config
struct (while swagger.json
path is default) panics and returns
panic: ./swagger.json file does not exist
System works fine without testing
Steps to reproduce the behavior:
swagger.json
located in ./swagger.json
ProvideSwagger()
not nilTestSwagger
should pass when ProvideSwagger()
not nil
=== RUN TestSwagger
--- PASS: TestSwagger (0.00s)
PASS
swagger/v1.1.0
func ProvideSwagger() *swagger.Config {
return &swagger.Config{
FilePath: "./swagger.json",
}
}
swag := ProvideSwagger()
func TestSwagger(t *testing.T) {
assert.NotNil(t, swag)
}
The middleware returned from otelfiber.Middleware()
always returns nil
, even if the next middleware in the stack returned an error.
When using multiple middlewares together, this can prevent a middleware earlier in the stack from also having access to the error. This is undesirable, for example, if an earlier-added logging middleware should also log the error (and which is outside of the fiber application's configured ErrorHandler).
According to the spec:
This should be obtained via configuration. If no such configuration can be obtained, this attribute MUST NOT be set
However, this instrumentation always sets http.server_name
even when the provided server name is empty
Because I started with air, I can see the specific panic information only when the project stops.
The contents are as follows:
panic: interface conversion: interface {} is nil, not *fiberi18n.Config
goroutine 6396 [running]:
github.com/gofiber/contrib/fiberi18n/v2.Localize(0x0?, {0x192c520?, 0xc00231f6f0})
C:/Users/Administrator/go/pkg/mod/github.com/gofiber/contrib/fiberi18n/[email protected]/i18n.go:106 +0x2cc
github.com/gofiber/contrib/fiberi18n/v2.MustLocalize(...)
C:/Users/Administrator/go/pkg/mod/github.com/gofiber/contrib/fiberi18n/[email protected]/i18n.go:85
github.com/biuaxia/crawling_FLiNGTrainer/pkg/i18n.Msg(0x9045ff?, {0x1a0f386, 0xf})
C:/Users/Administrator/GolandProjects/crawling_FLiNGTrainer/pkg/i18n/i18n.go:11 +0x33
github.com/biuaxia/crawling_FLiNGTrainer/pkg/resp.(*Resp).Build(0xc00231f910)
C:/Users/Administrator/GolandProjects/crawling_FLiNGTrainer/pkg/resp/resp.go:76 +0x3f
github.com/biuaxia/crawling_FLiNGTrainer/bootstrap.NewApplication.func2(0xc004080300, {0x1ca21a0, 0xc00029eb70})
C:/Users/Administrator/GolandProjects/crawling_FLiNGTrainer/bootstrap/bootstrap.go:109 +0xe49
github.com/gofiber/fiber/v2.(*App).ErrorHandler(0xc0004d0a00, 0xc004080300, {0x1ca21a0, 0xc00029eb70})
C:/Users/Administrator/go/pkg/mod/github.com/gofiber/fiber/[email protected]/app.go:1061 +0x1ff
github.com/gofiber/fiber/v2.(*App).serverErrorHandler(0xc0004d0a00, 0xc00231fc20?, {0x1ca3460, 0xc008fff590})
C:/Users/Administrator/go/pkg/mod/github.com/gofiber/fiber/[email protected]/app.go:1093 +0x2fb
github.com/valyala/fasthttp.(*Server).writeErrorResponse(0xc0002a6c00?, 0x0, 0xc001601800, {0x0, 0x0}, {0x1ca3460?, 0xc008fff590?})
C:/Users/Administrator/go/pkg/mod/github.com/valyala/[email protected]/server.go:2850 +0x57
github.com/valyala/fasthttp.(*Server).serveConn(0xc0002a6c00, {0x1ca93c0?, 0xc002afbc00})
C:/Users/Administrator/go/pkg/mod/github.com/valyala/[email protected]/server.go:2285 +0x1de5
github.com/valyala/fasthttp.(*workerPool).workerFunc(0xc0002b0500, 0xc002f83580)
C:/Users/Administrator/go/pkg/mod/github.com/valyala/[email protected]/workerpool.go:224 +0xa4
github.com/valyala/fasthttp.(*workerPool).getCh.func1()
C:/Users/Administrator/go/pkg/mod/github.com/valyala/[email protected]/workerpool.go:196 +0x32
created by github.com/valyala/fasthttp.(*workerPool).getCh in goroutine 1
C:/Users/Administrator/go/pkg/mod/github.com/valyala/[email protected]/workerpool.go:195 +0x1ab
panic: interface conversion: interface {} is nil, not *fiberi18n.Config
goroutine 6331 [running]:
github.com/gofiber/contrib/fiberi18n/v2.Localize(0x0?, {0x192c520?, 0xc001ac16f0})
C:/Users/Administrator/go/pkg/mod/github.com/gofiber/contrib/fiberi18n/[email protected]/i18n.go:106 +0x2cc
github.com/gofiber/contrib/fiberi18n/v2.MustLocalize(...)
C:/Users/Administrator/go/pkg/mod/github.com/gofiber/contrib/fiberi18n/[email protected]/i18n.go:85
github.com/biuaxia/crawling_FLiNGTrainer/pkg/i18n.Msg(0x9045ff?, {0x1a0f386, 0xf})
C:/Users/Administrator/GolandProjects/crawling_FLiNGTrainer/pkg/i18n/i18n.go:11 +0x33
github.com/biuaxia/crawling_FLiNGTrainer/pkg/resp.(*Resp).Build(0xc001ac1910)
C:/Users/Administrator/GolandProjects/crawling_FLiNGTrainer/pkg/resp/resp.go:76 +0x3f
github.com/biuaxia/crawling_FLiNGTrainer/bootstrap.NewApplication.func2(0xc004080000, {0x1ca21a0, 0xc00029eb70})
C:/Users/Administrator/GolandProjects/crawling_FLiNGTrainer/bootstrap/bootstrap.go:109 +0xe49
github.com/gofiber/fiber/v2.(*App).ErrorHandler(0xc0004d0a00, 0xc004080000, {0x1ca21a0, 0xc00029eb70})
C:/Users/Administrator/go/pkg/mod/github.com/gofiber/fiber/[email protected]/app.go:1061 +0x1ff
github.com/gofiber/fiber/v2.(*App).serverErrorHandler(0xc0004d0a00, 0xc001ac1c20?, {0x1ca3460, 0xc00051b680})
C:/Users/Administrator/go/pkg/mod/github.com/gofiber/fiber/[email protected]/app.go:1093 +0x2fb
github.com/valyala/fasthttp.(*Server).writeErrorResponse(0xc0002a6c00?, 0x0, 0xc005f81800, {0x0, 0x0}, {0x1ca3460?, 0xc00051b680?})
C:/Users/Administrator/go/pkg/mod/github.com/valyala/[email protected]/server.go:2850 +0x57
github.com/valyala/fasthttp.(*Server).serveConn(0xc0002a6c00, {0x1ca93c0?, 0xc002901c00})
C:/Users/Administrator/go/pkg/mod/github.com/valyala/[email protected]/server.go:2285 +0x1de5
This is the error message from fiber.Config 's ErrorHandler before I stopped the project:
2023-09-07 09:35:55 WARN bootstrap/bootstrap.go:93 ErrorHandler {"marshalString": "{\"error\":{\"code\":502,\"message\":\"Bad Gateway\"},\"errorMsg\":\"Bad Gateway\",\"exception\":{\"protocol\":\"https\",\"port\":\"62080\",\"route\":\"/\",\"resBody\":null,\"ips\":\"\",\"ua\":\"\",\"bytesSent\":0,\"queryParams\":\"\",\"requestid\":null,\"ip\":\"127.0.0.1\",\"host\":\"\",\"path\":\"/\",\"url\":\"/\",\"bytesReceived\":0,\"referer\":\"\",\"body\":\"{}\",\"reqHeaders\":{}}}"}
2023-09-07 09:35:55 WARN bootstrap/bootstrap.go:93 ErrorHandler {"marshalString": "{\"error\":{\"code\":502,\"message\":\"Bad Gateway\"},\"errorMsg\":\"Bad Gateway\",\"exception\":{\"body\":\"{}\",\"bytesReceived\":0,\"resBody\":null,\"requestid\":null,\"protocol\":\"https\",\"ua\":\"\",\"reqHeaders\":{},\"port\":\"61938\",\"path\":\"/\",\"bytesSent\":0,\"route\":\"/\",\"queryParams\":\"\",\"ips\":\"\",\"host\":\"\",\"url\":\"/\",\"referer\":\"\",\"ip\":\"127.0.0.1\"}}"}
This is my code for using i18n:
package i18n
import (
"github.com/duke-git/lancet/strutil"
"github.com/gofiber/contrib/fiberi18n/v2"
"github.com/gofiber/fiber/v2"
"github.com/nicksnyder/go-i18n/v2/i18n"
)
func Msg(c *fiber.Ctx, key string) string {
i18nMsg := fiberi18n.MustLocalize(c, key)
if !strutil.IsBlank(i18nMsg) {
return i18nMsg
}
return key
}
func MsgByTemplate(c *fiber.Ctx, msgKey string, msgParamMap map[string]string) string {
params := &i18n.LocalizeConfig{
MessageID: msgKey,
TemplateData: msgParamMap,
}
i18nMsg := fiberi18n.MustLocalize(c, params)
if !strutil.IsBlank(i18nMsg) {
return i18nMsg
}
return msgKey
}
Say hello to the maintainers of the entire open source software, and sincerely seek relevant solutions, if you need source code reproduction problems, you can provide an email, I will send it to you.
I'm not sure about the recurrence of this problem, which is usually caused by the use of the application process.
I want him to run normally, instead of staying in 502, and the whole application stops running completely.
v2.49.1
ErrorHandler:
ErrorHandler: func(c *fiber.Ctx, err error) error {
var body map[string]any
exceptionErr := c.BodyParser(&body)
if exceptionErr != nil {
body = make(map[string]any)
}
marshalBody, exceptionErr := encoder.Encode(body, encoder.SortMapKeys)
if exceptionErr != nil {
marshalBody = []byte(constant.ErrorDefaultMarshalBody)
}
exceptionMap := fiber.Map{
constant.ErrorExceptionMapKeyForError: err,
constant.ErrorExceptionMapKeyForErrorMsg: err.Error(),
constant.ErrorExceptionMapKeyForException: fiber.Map{
constant.FiberRequestId: c.Locals(constant.FiberRequestId),
logger.TagReferer: c.Get(fiber.HeaderReferer),
logger.TagProtocol: c.Protocol(),
logger.TagPort: c.Port(),
logger.TagIP: c.IP(),
logger.TagIPs: c.Get(fiber.HeaderXForwardedFor),
logger.TagHost: c.Hostname(),
logger.TagPath: c.Path(),
logger.TagURL: c.OriginalURL(),
logger.TagUA: c.Get(fiber.HeaderUserAgent),
logger.TagBody: string(marshalBody),
logger.TagBytesReceived: len(c.Request().Body()),
logger.TagBytesSent: len(c.Response().Body()),
logger.TagRoute: c.Route().Path,
logger.TagResBody: c.Response().Body(),
logger.TagReqHeaders: c.GetReqHeaders(),
logger.TagQueryStringParams: c.Request().URI().QueryArgs().String(),
},
}
marshalString, _ := sonic.MarshalString(exceptionMap)
zap.L().Warn("ErrorHandler", zap.String("marshalString", marshalString))
respData := resp.ERR(c).Data(exceptionMap)
var e *fiber.Error
if errors.As(err, &e) {
if e.Code == fiber.StatusNotFound {
return c.Status(fiber.StatusNotFound).SendString(i18n2.Msg(c, constant.I18N_RespPageNotFound))
}
}
return respData.Build()
}
Got this deprecation message:
SA1019: "go.opentelemetry.io/otel/oteltest" is deprecated: This package contains an alternate implementation of the OpenTelemetry SDK. This means it will diverge from the one commonly used in real world operations and therefore will not provide adequate testing guarantees for users. Because of this, this package should not be used to test performance or behavior of code running OpenTelemetry. Instead, the SpanRecorder from the go.opentelemetry.io/otel/sdk/trace/tracetest package can be registered with the default SDK (go.opentelemetry.io/otel/sdk/trace) as a SpanProcessor and used to test. This will ensure code will work with the default SDK. If users do not want to include a dependency on the default SDK it is recommended to run integration tests in their own module to isolate the dependency (see go.opentelemetry.io/otel/bridge/opencensus/test as an example).
https://pkg.go.dev/go.opentelemetry.io/otel/oteltest
This is the approach to use if moving to go.opentelemetry.io/otel/sdk/trace/tracetest
https://github.com/open-telemetry/opentelemetry-go/tree/main/bridge/opencensus/test
If this makes sense I can maybe prepare a PR for that.
No response
I'd like to have a custom field that are user defined zap.Fields.
Currently I have an client that want's http headers response into the logging file, that's post processed later. They want all headers, now the fiberzap doesn't have an option for it. Instead of just adding a "respHeaders" field it'll be more extensible to add a custom field that are user definied fields
package main
import (
"log"
"github.com/gofiber/contrib/fiberzap/v2"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
func main() {
app := fiber.New()
logger, _ := zap.NewProduction()
app.Use(fiberzap.New(fiberzap.Config{
Logger: logger,
Fields: []string{"custom"},
CustomFieldFunc: func(c *fiber.Ctx) []zap.Field {
var fields []zap.Field
headers := make(map[string]interface{})
c.Response().Header.VisitAll(func(k, v []byte) {
headers[string(k)] = string(v)
})
fields = append(fields, zap.Any("headers", headers))
return fields
},
}))
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
log.Fatal(app.Listen(":3000"))
}
Hi team!
I have an issue. Websocket middleware is not compatible with Recover middleware.
If panic happens in websocket handler, process will quit.
Possible reason:
In this code block, websocket middleware reigsters a handler to FastHTTPUpgrader.
if err := upgrader.Upgrade(c.Context(), func(fconn *websocket.Conn) {
conn.Conn = fconn
defer releaseConn(conn)
handler(conn)
}); err != nil { // Upgrading required
return fiber.ErrUpgradeRequired
}
FastHTTPUpgrader also registers a HijackHandler.
func (u *FastHTTPUpgrader) Upgrade(ctx *fasthttp.RequestCtx, handler FastHTTPHandler) error {
// ....
ctx.Hijack(func(netConn net.Conn) {
// ....
handler(c)
// ....
})
// ....
return nil
}
FastHttp runs hijackConnHandler in a new goroutine. If a panic occurs within this goroutine, the Recover middleware is unable to catch it.
func (s *Server) serveConn(c net.Conn) (err error) {
// ....
if hijackHandler != nil {
// ....
go hijackConnHandler(ctx, hjr, c, s, hijackHandler)
// ....
}
// ....
}
func main() {
app := fiber.New()
app.Use(recover.New(recover.Config{EnableStackTrace: true}))
app.Get("/foo", websocket.New(func(c *websocket.Conn) {
panic("foo")
}))
_ = app.Listen(":3000")
}
panic: foo
goroutine 20 [running]:
main.main.func1(0x6b8daf?)
/home/leo/project/main.go:16 +0x27
github.com/gofiber/contrib/websocket.New.func2.5(0x0?)
/home/leo/go/pkg/mod/github.com/gofiber/contrib/[email protected]/websocket.go:130 +0x75
github.com/fasthttp/websocket.(*FastHTTPUpgrader).Upgrade.func1({0x9cf848, 0xc0000961e0})
/home/leo/go/pkg/mod/github.com/fasthttp/[email protected]/server_fasthttp.go:201 +0x1bf
github.com/valyala/fasthttp.hijackConnHandler(0x0?, {0x9cb180?, 0xc00009c060}, {0x9cf950, 0xc000090008}, 0xc00027e800, 0xc0000a2100)
/home/leo/go/pkg/mod/github.com/valyala/[email protected]/server.go:2505 +0x68
created by github.com/valyala/fasthttp.(*Server).serveConn
/home/leo/go/pkg/mod/github.com/valyala/[email protected]/server.go:2460 +0x1c1c
websocket v1.0.0
package main
import "github.com/gofiber/contrib/%package%"
func main() {
// Steps to reproduce
}
Currently, otel middleware sets span name to route path. However, it's possible to register multiple routes with the same path but different methods. For example, POST /orders
and GET /orders
.
I believe, span name should include method along with path.
Currently there is no unit tests for websocket middleware, I would like to add it.
No response
No response
Hi, are there any changes needs to be done to read the telemetry baggage?
so we would have full flow between services?
currently, if call comes to the server, from a different server which is also using opentelemetry, we cannot see this relation
thanks!
How can I override config to log to file instead of terminal?
Currently, zerolog latency uses Dur
, which outputs raw duration value compared to other logging middlewares in fiber, changing it to String should make it in line with other logging behaviour.
No response
No response
OpenTelemetry metrics sdk is going to stabilize soon. It would be nice to add http metrics according to semconv
Use case: we use session middleware. When some middleware interrupts request processing before any handler invocation, the span name is always /
which is not informative and leads to grouping of all operations ended by any middleware.
Suggestion: Fiber supports names for routes. The instrumentation could check c.Route().Name
and if it's not empty, then use it as a span name instead of c.Route().Path
I want to close a websocket connection but nothing happens (no error etc.).
The client remains connected and can also send messages.
Steps to reproduce the behavior:
The server should close the connection after 5 seconds
websocket/v1.1.0
package main
import (
"log"
"time"
"github.com/gofiber/contrib/websocket"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
app.Use("/ws", middleware)
app.Get("/ws", websocket.New(handler))
log.Fatal(app.Listen(":8080"))
}
func middleware(c *fiber.Ctx) error {
if websocket.IsWebSocketUpgrade(c) {
return c.Next()
}
return fiber.ErrUpgradeRequired
}
func handler(conn *websocket.Conn) {
go func() {
time.Sleep(5 * time.Second)
if err := conn.Close(); err != nil {
log.Fatal(err)
}
}()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
log.Printf("msg: %s", msg)
}
}
I'm using github.com/gofiber/contrib/swagger and would like to write my spec in yaml format.
// Use swagger 2.0 middleware
app.Use(swagger.New(swagger.Config{
BasePath: "/",
FilePath: "./swagger.yaml",
}))
This works but in the UI the link to the file is set as /swagger.json
not /swagger.yaml
this causes the validator to fail:
(see lower right corner in the UI)
The second question I have is, how do I deal with multiple openapi yaml files? Can we use FilePath: "./spec/*
?
// Use swagger 2.0 middleware
app.Use(swagger.New(swagger.Config{
BasePath: "/",
FilePath: "./swagger.yaml",
}))
New version of opentelemetry-go/metric v.035.0, breaks this library.
../../../../go/pkg/mod/github.com/gofiber/contrib/[email protected]/fiber.go:55:35: meter.SyncFloat64 undefined (type metric.Meter has no field or method SyncFloat64)
../../../../go/pkg/mod/github.com/gofiber/contrib/[email protected]/fiber.go:59:38: meter.SyncInt64 undefined (type metric.Meter has no field or method SyncInt64)
../../../../go/pkg/mod/github.com/gofiber/contrib/[email protected]/fiber.go:63:39: meter.SyncInt64 undefined (type metric.Meter has no field or method SyncInt64)
../../../../go/pkg/mod/github.com/gofiber/contrib/[email protected]/fiber.go:67:41: meter.SyncInt64 undefined (type metric.Meter has no field or method SyncInt64)
When using the otelfiber middleware it would be nice to propergate the tracing headers to the outbound response in order to unify tracing across the call-chain. It would be needed to inject the coresponding headers from the TextMapPropagator, similar to the code snippet below.
This is already done in the limiter middleware for propergating the X-Ratelimit*
headers (also see https://github.com/gofiber/fiber/blob/master/middleware/limiter/limiter_fixed.go#L100-L102).
No response
func AddTracingHeadersToContext(ctx *fiber.Ctx) {
var tracingHeader = make(propagation.HeaderCarrier)
otel.GetTextMapPropagator().Inject(ctx.UserContext(), tracingHeader)
for _, headerKey := range tracingHeader.Keys() {
ctx.Set(headerKey, tracingHeader.Get(headerKey))
}
}
Hi, is it be useful to add OPA as middleware?
So in this way, we can add various policies as middleware like JWT authorization. WDYT?
I recently added Opentelemetry to a project I'm involved in. My setup includes using Zipkin as the collector and Fiber (otelfiber) and MongoDB (otelmongo) instrumentation to automate the capturing of their information. However, I've been facing an issue where the generated spans are not correctly setting the Parent ID, resulting in a lack of grouping for this information.
To ease the reproduction of this behavior, I've put together a simple code. In this code, I've launched two webservers: one using the Gin framework and the other utilizing Fiber. Both servers are configured to perform trace information collection.
As a result, requests directed to the Gin service generate spans grouped correctly. However, for requests to the Fiber service, the spans are not being grouped, leading to the situation I mentioned.
I would like to express my gratitude in advance for any guidance, suggestions, or insights that could be shared to assist in resolving this behavior. If anyone has encountered a similar situation or has any tips, I would be grateful for your contribution.
to reproduce the error:
// Clone repo
https://github.com/marcelobiao/gofiber-otel-issues
// run local containers
$task run
// In the browser:
Gin server:
http://localhost:8080/todo
Fiber server:
http://localhost:8081/todo
// Access Zipkin Client:
http://localhost:9411
When otelfiber is checking the Authorization
header it will always panic if the provided header doesn't have at least 5 of length.
Normally this is acceptable, but to prevent a unnecessary panic, we should check the length first and return "", false
if the length is invalid.
fiber.go:128
// Decode the header contents
raw, err := base64.StdEncoding.DecodeString(auth[6:])
if err != nil {
return "", false
}
I've tried updating to latest commit (as of today, it's feaa1ed), but it's not able to build due to the following errors, but I've seen the dependabots updated all the otel modules to v1.15.1 and passed GitHub Actions tests in this repo, I wonder why mine is not working?
Hi, I would like to add go newrelic as middleware to fiber if it feasible.
WDYT?
I will create a pr if it sounds feasible.
While running multiple requests in parallel fiber panics due to i18n translations. Of course it can be handled with recover
package but this is not ideal in my case.
panic: runtime error: slice bounds out of range [:-1]
goroutine 63 [running]:
github.com/valyala/fasthttp.releaseArg(...)
/Users/kacper/go/pkg/mod/github.com/valyala/[email protected]/args.go:469
github.com/valyala/fasthttp.(*Args).ParseBytes(0x1400028a200, {0x0?, 0x140004218b8?, 0x104cf4fc8?})
/Users/kacper/go/pkg/mod/github.com/valyala/[email protected]/args.go:102 +0x30c
github.com/valyala/fasthttp.(*URI).parseQueryArgs(...)
/Users/kacper/go/pkg/mod/github.com/valyala/[email protected]/uri.go:895
github.com/valyala/fasthttp.(*URI).QueryArgs(...)
/Users/kacper/go/pkg/mod/github.com/valyala/[email protected]/uri.go:887
github.com/valyala/fasthttp.(*RequestCtx).QueryArgs(0x1400028a000)
/Users/kacper/go/pkg/mod/github.com/valyala/[email protected]/server.go:989 +0x4c
github.com/gofiber/fiber/v2.(*Ctx).Query(0x1400044e000, {0x104d29384, 0x4}, {0x0, 0x0, 0x0?})
/Users/kacper/go/pkg/mod/github.com/gofiber/fiber/[email protected]/ctx.go:1050 +0x38
github.com/gofiber/contrib/fiberi18n.defaultLangHandler(0x860086?, {0x104d48450, 0x2})
/Users/kacper/go/pkg/mod/github.com/gofiber/contrib/[email protected]/config.go:82 +0x40
github.com/gofiber/contrib/fiberi18n.GetMessage({0x104e186a0?, 0x104e98b50})
/Users/kacper/go/pkg/mod/github.com/gofiber/contrib/[email protected]/i18n.go:101 +0x78
github.com/gofiber/contrib/fiberi18n.MustGetMessage(...)
/Users/kacper/go/pkg/mod/github.com/gofiber/contrib/[email protected]/i18n.go:83
main.main.func1(0x1400044e000)
/Users/kacper/GolandProjects/i18nbugfix/main.go:20 +0x30
github.com/gofiber/fiber/v2.(*App).next(0x14000080d80, 0x1400044e000)
/Users/kacper/go/pkg/mod/github.com/gofiber/fiber/[email protected]/router.go:144 +0x184
github.com/gofiber/fiber/v2.(*Ctx).Next(0x14000028488?)
/Users/kacper/go/pkg/mod/github.com/gofiber/fiber/[email protected]/ctx.go:909 +0x5c
github.com/gofiber/contrib/fiberi18n.New.func1(0x104e343c0?)
/Users/kacper/go/pkg/mod/github.com/gofiber/contrib/[email protected]/i18n.go:31 +0x78
github.com/gofiber/fiber/v2.(*App).next(0x14000080d80, 0x1400044e000)
/Users/kacper/go/pkg/mod/github.com/gofiber/fiber/[email protected]/router.go:144 +0x184
github.com/gofiber/fiber/v2.(*App).handler(0x14000080d80, 0x104c69dc0?)
/Users/kacper/go/pkg/mod/github.com/gofiber/fiber/[email protected]/router.go:171 +0x74
github.com/valyala/fasthttp.(*Server).serveConn(0x140001c6000, {0x104e9e838?, 0x1400048e180})
/Users/kacper/go/pkg/mod/github.com/valyala/[email protected]/server.go:2363 +0xdd0
github.com/valyala/fasthttp.(*workerPool).workerFunc(0x140000d2fa0, 0x14000068360)
/Users/kacper/go/pkg/mod/github.com/valyala/[email protected]/workerpool.go:224 +0x70
github.com/valyala/fasthttp.(*workerPool).getCh.func1()
/Users/kacper/go/pkg/mod/github.com/valyala/[email protected]/workerpool.go:196 +0x38
created by github.com/valyala/fasthttp.(*workerPool).getCh
/Users/kacper/go/pkg/mod/github.com/valyala/[email protected]/workerpool.go:195 +0x220
Steps to reproduce the behavior:
ab -n 10000 -c 50 http://127.0.0.1:3000/
few times (just wait for each of it to finish)Program should work and return translated response without panicking
v1.0.0
package main
import (
"github.com/gofiber/contrib/fiberi18n"
"github.com/gofiber/fiber/v2"
"golang.org/x/text/language"
)
func main() {
app := fiber.New()
app.Use(
fiberi18n.New(&fiberi18n.Config{
RootPath: "./localize",
AcceptLanguages: []language.Tag{language.English, language.Polish},
FormatBundleFile: "json",
DefaultLanguage: language.English,
}),
)
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString(fiberi18n.MustGetMessage("hello"))
})
app.Listen("127.0.0.1:3000")
}
Currently, otel middleware add all the attributes manually.
I think it's better to use semconv functions for cases when fasthttp.Request
is compatible with http.Request
because this will automatically add all recommended attributes. This is easier to maintain because following spec requires semconv package update only.
Besides, I think it should use SpanStatusFromHTTPStatusCodeAndSpanKind
instead of SpanStatusFromHTTPStatusCode
According to https://go.dev/doc/modules/managing-source#multiple-module-source, when you have multiple modules in a single repository, version tags must have the module name as a prefix. Otherwise, attempting to use tagged versions would fail:
โฏ go get github.com/gofiber/contrib/[email protected]
go: module github.com/gofiber/[email protected] found, but does not contain package github.com/gofiber/contrib/fiberzap
Instead of having a single v1.0.1
tag, tag each module individually.
Something that was done for fiberzap but for otelfiber: #272
No response
No response
I've been using otel-fiber for a while, and I've noticed that by editing the fiber.go (around line 123) file in this way, I can set the main span status to error:
if err := c.Next(); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Message) //<-- added this line
// invokes the registered HTTP error handler
// to get the correct response status code
_ = c.App().Config().ErrorHandler(c, err)
}
Now, this solution works every time there is an error returned, but clearly not when there is a panic inside the handler function or one of the sub-functions, since no error is returned from the handler. I checked with breaks in debug mode, and the program never enters the if
statemen, so the span is not set to Error.
What sounds strange is that the error is still recorded and logged as "event" in the span, and i can find it in the Uptrace application, with all the exception-related info, even if span.RecordError(err)
is (apparently) never called.
Can anyone explain this behavior? I'm sure I'm missing something.
Is worth noting that i am using the recover
middleware, in this order:
router.Use(logger.New())
router.Use(recover.New())
router.Use(cors.New())
router.Use(otelfiber.Middleware())
Thanks
No response
Is there support for the openapi standard v3? When I try to load a openapi v3 spec it fails
I can see that the latest version of swagger-ui is being used as the bundle link is https://unpkg.com/[email protected]/swagger-ui-bundle.js which shows version 5.3.1 of swagger-ui. So I would expect openapi v3 to work. Maybe this problem is related to the default file name of swagger.json
?
openapi: 3.0.0
info:
version: 1.0.0
title: Simple API Example
description: A basic API example for testing Swagger UI
paths:
/greet:
get:
summary: Get a friendly greeting
responses:
'200':
description: Successful response
content:
application/json:
example:
message: Hello, Swagger UI!
Currently in fiber we have a mechanism that synchronizes the documentation into the docs repository.
https://github.com/gofiber/fiber/blob/master/.github/workflows/sync-docs.yml ->
https://github.com/gofiber/fiber/tree/master/docs ->
https://github.com/gofiber/docs
The idea is to have a similar process that provides further versioned documentation for the other packages and extends the documentation page, besides the core documentation with the documentation about these packages.
https://github.com/gofiber/contrib ->
https://docusaurus.io/docs/next/docs-multi-instance#versioned-and-unversioned-doc
This will also make them more present and more enhancements, bugfixes or similar improvements can be expected
Greetings,
Currently, NewRelic middleware requires the configuration parameters to be passed and creates a new NewRelic application. However, on the project I am working on, I already have a NewRelic app and I use it some other places as well. I totally understand the basic-friendly approach, however, can we also have a function something like fibernewrelic.NewWithApp(app *newrelic.Application)
that enables using the existing app? If so, I can start a PR that implements such a change.
I have raised a bug issue in opentelemetry-go SDK repository.
When using fiber to manually reporting request count with delta temporality, the server will keep reporting wrong attributes value and wrong datapoint size infinitely.
While using go http library or something else, everything work as expected so I got the right request count.
See my demo repository:
https://github.com/oliverdding/otel-test
Start with:
docker compose up -d --build
Or you can run go cmd program manually with:
docker compose up -d otel-collector
There should be ten datapoints, with app_id from 1 to 10.
v2.49.2
package main
import (
"context"
"fmt"
"math/rand"
"net/http"
"os"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/metric"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
logger *zap.Logger
)
func init() {
var cfg zap.Config
cfg = zap.NewDevelopmentConfig()
// set time format
cfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
// add color for console
cfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
logger = zap.New(zapcore.NewCore(zapcore.NewConsoleEncoder(cfg.EncoderConfig), zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)), zapcore.DebugLevel))
}
func preferDeltaTemporalitySelector(kind sdkmetric.InstrumentKind) metricdata.Temporality {
switch kind {
case sdkmetric.InstrumentKindCounter,
sdkmetric.InstrumentKindObservableCounter,
sdkmetric.InstrumentKindHistogram:
return metricdata.DeltaTemporality
default:
return metricdata.CumulativeTemporality
}
}
func initProvider(ctx context.Context) func() {
res, err := resource.New(ctx,
resource.WithFromEnv(),
resource.WithProcess(),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithAttributes(
semconv.ServiceName("client"),
semconv.ServiceNamespace("demo"),
semconv.ServiceVersion("0.1.0"),
),
)
if err != nil {
logger.Fatal("failed to create resource", zap.Error(err))
}
otelAgentAddr, ok := os.LookupEnv("OTEL_EXPORTER_OTLP_ENDPOINT")
if !ok {
otelAgentAddr = "127.0.0.1:4317"
}
metricExp, err := otlpmetricgrpc.New(
ctx,
otlpmetricgrpc.WithInsecure(),
otlpmetricgrpc.WithEndpoint(otelAgentAddr),
otlpmetricgrpc.WithCompressor("gzip"),
otlpmetricgrpc.WithTemporalitySelector(preferDeltaTemporalitySelector),
)
meterProvider := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(
sdkmetric.NewPeriodicReader(
metricExp,
sdkmetric.WithInterval(2*time.Second),
),
),
)
otel.SetMeterProvider(meterProvider)
return func() {
cxt, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
// pushes any last exports to the receiver
if err := meterProvider.Shutdown(cxt); err != nil {
otel.Handle(err)
}
}
}
var (
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
meter metric.Meter
appIDList = []string{"1024", "568", "106", "1025", "197"}
)
func main() {
ctx := context.Background()
otelShutdownHook := initProvider(ctx)
defer otelShutdownHook()
meter = otel.Meter("demo-server")
requestLatency, _ := meter.Float64Histogram(
"demo_client/request_latency",
metric.WithDescription("The latency of requests processed"),
)
requestCount, _ := meter.Int64Counter(
"demo_client/request_counts",
metric.WithDescription("The number of requests processed"),
)
for {
appID := appIDList[rng.Intn(len(appIDList))]
startTime := time.Now()
makeHelloRequest(ctx, appID)
latencyMs := float64(time.Since(startTime)) / 1e6
requestLatency.Record(ctx, latencyMs, metric.WithAttributes(attribute.String("interface", "hello"), attribute.String("app_id", appID)))
requestCount.Add(ctx, 1, metric.WithAttributes(attribute.String("interface", "hello"), attribute.String("app_id", appID)))
fmt.Printf("Latency: %.3fms\n", latencyMs)
time.Sleep(time.Duration(1) * time.Second)
}
}
func makeHelloRequest(ctx context.Context, appID string) {
demoServerAddr, ok := os.LookupEnv("DEMO_SERVER_ENDPOINT")
if !ok {
demoServerAddr = "0.0.0.0:2333"
}
// Trace an HTTP client by wrapping the transport
client := http.Client{
Transport: otelhttp.NewTransport(http.DefaultTransport),
}
// Make sure we pass the context to the request to avoid broken traces.
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://%s/hello/%s", demoServerAddr, appID), nil)
if err != nil {
logger.Fatal("failed to create request", zap.Error(err))
}
// All requests made with this client will create spans.
res, err := client.Do(req)
if err != nil {
logger.Fatal("failed to execute request", zap.Error(err))
}
res.Body.Close()
}
add to OpenTelemetry registry after #19 is merged
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.