@@ -10,21 +10,23 @@ import (
1010 "net"
1111 "net/http"
1212 "time"
13+
14+ "github.com/hanwen/go-fuse/v2/fuse"
1315)
1416
17+
1518type Client struct {
16- http * http.Client
17- socketPath string
19+ http * http.Client
20+ socketPath string
1821}
1922
2023func NewClient (socketPath string ) * Client {
21- return & Client {
22- http : NewUnixSocketClient (socketPath ),
23- socketPath : socketPath ,
24- }
24+ return & Client {
25+ http : NewUnixSocketClient (socketPath ),
26+ socketPath : socketPath ,
27+ }
2528}
2629
27-
2830func NewUnixSocketClient (socketPath string ) * http.Client {
2931 return & http.Client {
3032 Transport : & http.Transport {
@@ -59,35 +61,122 @@ func (client *Client) NotifyReady(logger *slog.Logger) error {
5961 return nil
6062}
6163
62- func (client * Client ) Post (context context.Context , path OperationPath , in any , out any ) error {
63- body , err := json .Marshal (in )
64- if err != nil {
65- return fmt .Errorf ("failed to marshal request: %w" , err )
66- }
67- url := serverURL + string (path )
68- req , err := http .NewRequestWithContext (context , http .MethodPost , url , bytes .NewBuffer (body ))
69- if err != nil {
70- return fmt .Errorf ("error creating Post request: %w" , err )
64+ // Post sends a JSON body to the given operation path and returns an errno.
65+ // A non-200 HTTP response means a transport failure so we return fuse.EIO without reading the body.
66+ // uppon 200, the response always contains an errno field: non-zero means the operation failed with that errno,
67+ // zero means success and the remaining fields are the operation's data, unmarshalled into out if non-nil.
68+ func (client * Client ) Post (context context.Context , path OperationPath , in any , out any ) fuse.Status {
69+ body , err := json .Marshal (in )
70+ if err != nil {
71+ return fuse .EIO
72+ }
73+ url := serverURL + string (path )
74+ req , err := http .NewRequestWithContext (context , http .MethodPost , url , bytes .NewBuffer (body ))
75+ if err != nil {
76+ return fuse .EIO
7177 }
78+ req .Header .Set ("Content-Type" , "application/json" )
7279
73- req .Header .Set ("Content-Type" , "application/json" )
80+ resp , err := client .http .Do (req )
81+ if err != nil {
82+ return fuse .EIO
83+ }
84+ defer func () { _ = resp .Body .Close () }()
7485
75- resp , err := client .http .Do (req )
86+ if resp .StatusCode != http .StatusOK {
87+ return fuse .EIO
88+ }
89+
90+ resBody , err := io .ReadAll (resp .Body )
7691 if err != nil {
77- return fmt .Errorf ("sending Post request: %w" , err )
92+ return fuse .EIO
93+ }
94+
95+ var errResp ErrorResponse
96+ if err = json .Unmarshal (resBody , & errResp ); err == nil && errResp .Errno != 0 {
97+ return fuse .Status (errResp .Errno )
7898 }
79- defer func () { _ = resp .Body .Close () }()
8099
81- if resp .StatusCode != http .StatusOK {
82- return fmt .Errorf ("unexpected status from Post endpoint: %d" , resp .StatusCode )
100+ if out != nil {
101+ if err = json .Unmarshal (resBody , out ); err != nil {
102+ return fuse .EIO
103+ }
104+ }
105+
106+ return fuse .OK
107+ }
108+
109+ // PostBinary sends a JSON body to the given operation path and returns raw binary data.
110+ // Errors are signaled via the X-Errno response header (non-zero = fuse.Status error code).
111+ // On success (X-Errno: 0) the response body is raw bytes copied into dest.
112+ // Returns the number of bytes read and a fuse.Status.
113+ func (client * Client ) PostBinary (ctx context.Context , path OperationPath , in any , dest []byte ) (int , fuse.Status ) {
114+ body , err := json .Marshal (in )
115+ if err != nil {
116+ return 0 , fuse .EIO
83117 }
84- resBody , err := io .ReadAll (resp .Body )
85- if err != nil {
86- return fmt .Errorf ("failed to read response body: %w" , err )
118+ url := serverURL + string (path )
119+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , url , bytes .NewBuffer (body ))
120+ if err != nil {
121+ return 0 , fuse .EIO
87122 }
88- err = json .Unmarshal (resBody , out )
89- if err != nil {
90- return fmt .Errorf ("failed to unmarshal response: %w" , err )
123+ req .Header .Set ("Content-Type" , "application/json" )
124+
125+ resp , err := client .http .Do (req )
126+ if err != nil {
127+ return 0 , fuse .EIO
91128 }
92- return nil
129+ defer func () { _ = resp .Body .Close () }()
130+
131+ if resp .StatusCode != http .StatusOK {
132+ return 0 , fuse .EIO
133+ }
134+
135+ if errnoStr := resp .Header .Get ("X-Errno" ); errnoStr != "" && errnoStr != "0" {
136+ var errno int32
137+ if _ , err := fmt .Sscanf (errnoStr , "%d" , & errno ); err == nil && errno != 0 {
138+ return 0 , fuse .Status (errno )
139+ }
140+ }
141+
142+ bytesRead , err := io .ReadFull (resp .Body , dest )
143+ if err != nil && err != io .ErrUnexpectedEOF {
144+ return 0 , fuse .EIO
145+ }
146+ return bytesRead , fuse .OK
147+ }
148+
149+ // PostBinaryRequest sends raw binary payload to the given operation path.
150+ // Errors are signaled via the X-Errno response header (non-zero = fuse.Status error code).
151+ // On success it returns fuse.OK.
152+ func (client * Client ) PostSendBinary (ctx context.Context , path OperationPath , payload []byte , headers map [string ]string ) fuse.Status {
153+ url := serverURL + string (path )
154+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , url , bytes .NewBuffer (payload ))
155+ if err != nil {
156+ return fuse .EIO
157+ }
158+ req .Header .Set ("Content-Type" , "application/octet-stream" )
159+ for key , value := range headers {
160+ req .Header .Set (key , value )
161+ }
162+
163+ resp , err := client .http .Do (req )
164+ if err != nil {
165+ return fuse .EIO
166+ }
167+ defer func () { _ = resp .Body .Close () }()
168+
169+ if resp .StatusCode != http .StatusOK {
170+ return fuse .EIO
171+ }
172+
173+ if errnoStr := resp .Header .Get ("X-Errno" ); errnoStr != "" && errnoStr != "0" {
174+ var errno int32
175+ if _ , err := fmt .Sscanf (errnoStr , "%d" , & errno ); err == nil && errno != 0 {
176+ return fuse .Status (errno )
177+ }
178+ return fuse .EIO
179+ }
180+
181+ return fuse .OK
93182}
0 commit comments