Summary
When using github.com/go-telegram/bot v1.21.0 with a local Telegram Bot API server (telegram-bot-api --local), requests may fail with:
error decode response body for method getMe:
unexpected end of JSON input
After investigation, the issue appears to be related to chunked transfer encoding.
rawRequest() streams request bodies via io.Pipe(), causing Go to send:
Transfer-Encoding: chunked
because the request body length is unknown.
Some local telegram-bot-api --local servers appear to reject chunked request bodies and respond with:
HTTP 400 Bad Request
Content-Length: 0
which eventually leads to the JSON decoding failure.
Environment
- Go: go1.25.0 darwin/arm64
- github.com/go-telegram/bot: v1.21.0
- telegram-bot-api: local mode (
--local)
- OS: macOS (arm64)
Steps to reproduce
1. Start local Bot API server
telegram-bot-api \
--api-id=xxx \
--api-hash=xxx \
--local \
--http-port=7070
2. Run code
package main
import (
"log"
"github.com/go-telegram/bot"
)
func main() {
b, err := bot.New(
"YOUR_BOT_TOKEN",
bot.WithServerURL("http://127.0.0.1:7070"),
)
if err != nil {
log.Fatal(err)
}
_ = b
}
Observed behavior
Request:
POST /bot<TOKEN>/getMe
Transfer-Encoding: chunked
Content-Type: multipart/form-data; boundary=...
Response:
HTTP 400 Bad Request
Content-Length: 0
Client error:
error call getMe, error decode response body for method getMe, unexpected end of JSON input
Root cause
rawRequest() creates a streaming request body:
pr, pw := io.Pipe()
form := multipart.NewWriter(pw)
Because the body length is unknown, Go automatically uses:
Transfer-Encoding: chunked
instead of:
The local Bot API server appears to reject chunked request bodies and returns an empty 400 response.
Therefore, the issue seems to be related to chunked transfer encoding rather than multipart itself.
Additional verification
Sending a request with a known Content-Length works correctly.
For example:
curl -X POST \
-H "Content-Type: application/json" \
-d 'null' \
http://127.0.0.1:7070/bot<TOKEN>/getMe
returns:
Also, buffering the request body and explicitly setting:
req.ContentLength = int64(len(buf))
req.TransferEncoding = nil
eliminates the problem.
Workaround
A custom RoundTripper that converts unknown-length request bodies into fixed-length bodies resolves the issue:
type contentLengthTransport struct {
base http.RoundTripper
}
func (t *contentLengthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if req.Body != nil && req.ContentLength <= 0 {
buf, err := io.ReadAll(req.Body)
_ = req.Body.Close()
if err != nil {
return nil, err
}
if len(buf) == 0 {
req.Body = http.NoBody
req.GetBody = func() (io.ReadCloser, error) {
return http.NoBody, nil
}
} else {
req.Body = io.NopCloser(bytes.NewReader(buf))
req.GetBody = func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(buf)), nil
}
}
req.ContentLength = int64(len(buf))
req.TransferEncoding = nil
}
return t.base.RoundTrip(req)
}
Usage:
transport := http.DefaultTransport
client := &http.Client{
Transport: &contentLengthTransport{
base: transport,
},
Timeout: 10 * time.Second,
}
b, err := bot.New(
token,
bot.WithHTTPClient(10*time.Second, client),
bot.WithServerURL("http://127.0.0.1:7070"),
)
if err != nil {
log.Fatal(err)
}
With this custom transport, bot.New() and subsequent API calls work correctly against the local Bot API server.
Possible improvement
Instead of streaming multipart requests through io.Pipe(), buffering the body and sending it with a known Content-Length may improve compatibility with local Bot API servers.
For example:
var body bytes.Buffer
form := multipart.NewWriter(&body)
// write fields ...
form.Close()
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
u,
&body,
)
req.ContentLength = int64(body.Len())
which results in:
instead of:
Transfer-Encoding: chunked
```.
Summary
When using
github.com/go-telegram/botv1.21.0 with a local Telegram Bot API server (telegram-bot-api --local), requests may fail with:After investigation, the issue appears to be related to chunked transfer encoding.
rawRequest()streams request bodies viaio.Pipe(), causing Go to send:Transfer-Encoding: chunkedbecause the request body length is unknown.
Some local
telegram-bot-api --localservers appear to reject chunked request bodies and respond with:which eventually leads to the JSON decoding failure.
Environment
--local)Steps to reproduce
1. Start local Bot API server
2. Run code
Observed behavior
Request:
Response:
Client error:
Root cause
rawRequest()creates a streaming request body:Because the body length is unknown, Go automatically uses:
Transfer-Encoding: chunkedinstead of:
Content-Length: xxxThe local Bot API server appears to reject chunked request bodies and returns an empty 400 response.
Therefore, the issue seems to be related to chunked transfer encoding rather than multipart itself.
Additional verification
Sending a request with a known
Content-Lengthworks correctly.For example:
returns:
Also, buffering the request body and explicitly setting:
eliminates the problem.
Workaround
A custom
RoundTripperthat converts unknown-length request bodies into fixed-length bodies resolves the issue:Usage:
With this custom transport,
bot.New()and subsequent API calls work correctly against the local Bot API server.Possible improvement
Instead of streaming multipart requests through
io.Pipe(), buffering the body and sending it with a knownContent-Lengthmay improve compatibility with local Bot API servers.For example:
which results in:
Content-Length: xxxinstead of: