项目文件夹

文件
Asim Aslam 1bb25d6e7f Add agent platform showcase and refactor project structure (#2884)
* feat: add agent platform showcase and blog post

Add a complete platform example (Users, Posts, Comments, Mail) that
mirrors micro/blog, demonstrating how existing microservices become
AI-accessible through MCP with zero code changes.

Includes blog post "Your Microservices Are Already an AI Platform"
walking through real agent workflows: signup, content creation,
commenting, tagging, and cross-service messaging.

https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc

* refactor: rename handler types to drop redundant Service suffix

UserService → Users, PostService → Posts, CommentService → Comments,
MailService → Mail. Matches micro/blog naming convention.

https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc

* refactor: consolidate top-level directories, reduce framework bloat

Move internal/non-public packages behind internal/ or into their
parent packages where they belong:

- deploy/ → gateway/mcp/deploy/ (Helm charts belong with the gateway)
- profile/ → service/profile/ (preset plugin profiles are a service concern)
- scripts/ → internal/scripts/ (install script is not public API)
- test/ → internal/test/ (test harness is not public API)
- util/ → internal/util/ (internal helpers shouldn't be imported externally)

Also fixes CLAUDE.md merge conflict markers and updates project
structure documentation.

All import paths updated. Build and tests pass.

https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc

* refactor: redesign model package to match framework conventions

Rename model.Database interface to model.Model (consistent with
client.Client, server.Server, store.Store). Remove generics in
favor of interface{}-based API with reflection.

Key changes:
- model.Model interface: Register once, CRUD infers table from type
- DefaultModel + NewModel() + package-level convenience functions
- Schema registered via Register(&User{}), no per-call schema passing
- Memory implementation as default (in model package, like store)
- memory/sqlite/postgres backends updated for new interface
- protoc-gen-micro generates RegisterXModel() instead of generic factory
- All docs, blog, and README updated

https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-05 11:21:41 +00:00

217 行
3.3 KiB
Go

package transport
import (
"bufio"
"bytes"
"io"
"net"
"net/http"
"net/url"
"sync"
"time"
"github.com/pkg/errors"
log "go-micro.dev/v5/logger"
"go-micro.dev/v5/internal/util/buf"
)
type httpTransportClient struct {
dialOpts DialOptions
conn net.Conn
ht *httpTransport
// request must be stored for response processing
req chan *http.Request
buff *bufio.Reader
addr string
// local/remote ip
local string
remote string
reqList []*http.Request
sync.RWMutex
once sync.Once
closed bool
}
func (h *httpTransportClient) Local() string {
return h.local
}
func (h *httpTransportClient) Remote() string {
return h.remote
}
func (h *httpTransportClient) Send(m *Message) error {
logger := h.ht.Options().Logger
header := make(http.Header)
for k, v := range m.Header {
header.Set(k, v)
}
b := buf.New(bytes.NewBuffer(m.Body))
defer func() {
if err := b.Close(); err != nil {
logger.Logf(log.ErrorLevel, "failed to close buffer: %v", err)
}
}()
req := &http.Request{
Method: http.MethodPost,
URL: &url.URL{
Scheme: "http",
Host: h.addr,
},
Header: header,
Body: b,
ContentLength: int64(b.Len()),
Host: h.addr,
Close: h.dialOpts.ConnClose,
}
if !h.dialOpts.Stream {
h.Lock()
if h.closed {
h.Unlock()
return io.EOF
}
h.reqList = append(h.reqList, req)
select {
case h.req <- h.reqList[0]:
h.reqList = h.reqList[1:]
default:
}
h.Unlock()
}
// set timeout if its greater than 0
if h.ht.opts.Timeout > time.Duration(0) {
if err := h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout)); err != nil {
return err
}
}
return req.Write(h.conn)
}
// Recv receives a message.
func (h *httpTransportClient) Recv(msg *Message) (err error) {
if msg == nil {
return errors.New("message passed in is nil")
}
var req *http.Request
if !h.dialOpts.Stream {
var rc *http.Request
var ok bool
h.Lock()
select {
case rc, ok = <-h.req:
default:
}
if !ok {
if len(h.reqList) == 0 {
h.Unlock()
return io.EOF
}
rc = h.reqList[0]
h.reqList = h.reqList[1:]
}
h.Unlock()
req = rc
}
// set timeout if its greater than 0
if h.ht.opts.Timeout > time.Duration(0) {
if err = h.conn.SetDeadline(time.Now().Add(h.ht.opts.Timeout)); err != nil {
return err
}
}
h.Lock()
defer h.Unlock()
if h.closed {
return io.EOF
}
rsp, err := http.ReadResponse(h.buff, req)
if err != nil {
return err
}
defer func() {
if err2 := rsp.Body.Close(); err2 != nil {
err = errors.Wrap(err2, "failed to close body")
}
}()
b, err := io.ReadAll(rsp.Body)
if err != nil {
return err
}
if rsp.StatusCode != http.StatusOK {
return errors.New(rsp.Status + ": " + string(b))
}
msg.Body = b
if msg.Header == nil {
msg.Header = make(map[string]string, len(rsp.Header))
}
for k, v := range rsp.Header {
if len(v) > 0 {
msg.Header[k] = v[0]
} else {
msg.Header[k] = ""
}
}
return nil
}
func (h *httpTransportClient) Close() error {
if !h.dialOpts.Stream {
h.once.Do(
func() {
h.Lock()
h.buff.Reset(nil)
h.closed = true
h.Unlock()
close(h.req)
},
)
return h.conn.Close()
}
err := h.conn.Close()
h.once.Do(
func() {
h.Lock()
h.buff.Reset(nil)
h.closed = true
h.Unlock()
close(h.req)
},
)
return err
}