Compare commits

...

12 Commits

Author SHA1 Message Date
Jeff Carr 1905db1f6a add something to print out a http PB 2025-09-22 01:50:47 -05:00
Jeff Carr 3306e3cbbc add more missing fields to the .proto 2025-09-09 18:02:31 -05:00
Jeff Carr cd656f489e add Log() and Logf() 2025-09-09 17:15:08 -05:00
Jeff Carr c0f0414ff8 smarter variable name. predicts a logpb 2025-09-09 17:05:05 -05:00
Jeff Carr 47cd147ca0 common write file function 2025-09-09 14:53:42 -05:00
Jeff Carr 6077c54d22 moved this to httppb 2025-09-08 12:53:35 -05:00
Jeff Carr eaf3e1d5a7 add a Log 2025-09-08 12:42:37 -05:00
Jeff Carr 09c36a87b3 track client & server payload sizes 2025-09-08 08:13:40 -05:00
Jeff Carr f67d618413 generic POST function 2025-09-08 04:35:28 -05:00
Jeff Carr aef7b966ff change the names from the http standards
I'll probably regret this and if anyone ever sees this and
	they actually use it, some people will probably complain. so: sorry
	It's not like HTTP isn't really well documented these days :)
	anyway, the *Addr fields are, I think, really really old
	kinda dumb names that were made to be super unique and ackward
	like RemoteAddr or whatever. Back then, having 'IP' would have
	been a major problem and PITA and super confusing. So I think
	everyone (I want to say 'we' but I don't know if I had anything
	to do with RemoteAddr. I think I did add the Apache guys to
	add SERVER_NAME support. Before that I don't think apache could
	respond to mosaic as 2 different hostnames. My memory could be
	incorrect.) named some of these things in ways that don't make
	sense in a protobuf like this where it should simply be IP
2025-09-07 21:41:28 -05:00
Jeff Carr 4b78407d3f store the errors 2025-09-07 21:41:24 -05:00
Jeff Carr e5cd37ef21 add req to PB function 2025-09-06 18:49:07 -05:00
8 changed files with 385 additions and 16 deletions

86
httpRequest.dump.go Normal file
View File

@ -0,0 +1,86 @@
package httppb
import (
"fmt"
"go.wit.com/log"
)
func (pb *HttpRequest) DumpRemoteAddr() string {
return pb.RemoteAddr
}
func (pb *HttpRequest) DumpUserAgent() string {
var all string
for param, values := range pb.Query {
for _, value := range values {
all += fmt.Sprint(" Query:", param, value)
}
}
// hostname := r.URL.Query().Get("hostname")
return pb.GetUserAgent() + all
}
// todo: convert this code
func (pb *HttpRequest) DumpClient() {
log.Infof("%s %s %s\n", pb.Host, pb.URL, pb.RemoteAddr)
/*
var host, url, proto, addr, agent string
host = r.Host
url = r.URL.String()
proto = r.Proto
addr = r.RemoteAddr
agent = r.UserAgent()
log.Warn(host, proto, addr, url, agent)
fmt.Fprintln(accessf, time.Now(), host, proto, addr, url, agent)
// return
fmt.Fprintln(clientf)
fmt.Fprintln(clientf, time.Now())
// Basic request information
fmt.Fprintln(clientf, "Method:", r.Method)
fmt.Fprintln(clientf, "URL:", r.URL)
fmt.Fprintln(clientf, "Protocol:", r.Proto)
fmt.Fprintln(clientf, "Host:", r.Host)
fmt.Fprintln(clientf, "Remote Address:", r.RemoteAddr)
// Headers
fmt.Fprintln(clientf, "Headers:")
for name, values := range r.Header {
for _, value := range values {
fmt.Fprintln(clientf, "Headers:", name, value)
}
}
// Query parameters
fmt.Fprintln(clientf, "Query Parameters:")
for param, values := range r.URL.Query() {
for _, value := range values {
fmt.Fprintln(clientf, "Query:", param, value)
}
}
// User-Agent
fmt.Fprintln(clientf, "User-Agent:", r.UserAgent())
// Content Length
fmt.Fprintln(clientf, "Content Length:", r.ContentLength)
// Cookies
fmt.Fprintln(clientf, "Cookies:")
for _, cookie := range r.Cookies() {
fmt.Fprintln(clientf, cookie.Name, cookie.Value)
}
// Request Body (if applicable)
if r.Body != nil {
body, err := ioutil.ReadAll(r.Body)
if err == nil {
fmt.Fprintln(clientf, "Body:", string(body))
}
}
*/
}

View File

@ -4,24 +4,34 @@ syntax = "proto3";
package httppb; package httppb;
// todo: try this
// import "google/rpc/status.proto";
// google.rpc.Status error = 13;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
message HttpRequest { // HttpRequest represents the essential fields of an incoming HTTP request. message HttpRequest { // HttpRequest represents the essential fields of an incoming HTTP request.
string method = 1; // The request method, e.g., "GET", "POST". string method = 1; // The request method, e.g., "GET", "POST".
string url = 2; // The full URL of the request, including scheme, host, path, and query string. string URL = 2; // The full URL of the request, including scheme, host, path, and query string.
string route = 3; // just the route: "/add/" or "/find/" string route = 3; // just the route: "/add/" or "/find/"
string proto = 4; // The protocol version, e.g., "HTTP/1.1", "HTTP/2.0". string proto = 4; // The protocol version, e.g., "HTTP/1.1", "HTTP/2.0".
map<string, string> headers = 5; // The map of request headers. Header names are case-insensitive, map<string, string> headers = 5; // The map of request headers. Header names are case-insensitive,
string remoteAddr = 6; // The remote IP address of the client, after resolving proxies. string IP = 6; // The remote IP address of the client, after resolving proxies.
string host = 7; // The host on which the URL is sought (www.wit.com) string host = 7; // The host on which the URL is sought (www.wit.com)
string hostname = 8; // The hostname of the client if passed from the client (mylaptop.fun.me) string hostname = 8; // The hostname of the client if passed from the client (mylaptop.fun.me)
bytes body = 9; // The request body as raw bytes. string namespace = 9; // This is the pb namespace ("go.wit.com/lib/protobuf/virtpb")
string namespace = 10; // When the body is a pb (always!). This is the pb namespace ("go.wit.com/lib/protobuf/virtpb") google.protobuf.Timestamp ctime = 10; // create time of the patch
google.protobuf.Timestamp ctime = 11; // create time of the patch bytes clientData = 11; // the client payload
int64 clientDataLen = 12; // len(body)
bytes serverData = 13; // the server response
int64 serverDataLen = 14; // len(data)
repeated string logs = 15; // use this to store whatever you want while the whole POST happens
string userAgent = 16; // client user-agent
map<string, string> query = 17; // r.URL.Query()
string remoteAddr = 18; // r.RemoteAddr
} }
message HttpRequests { // `autogenpb:marshal` `autogenpb:mutex` message HttpRequests { // `autogenpb:marshal` `autogenpb:mutex`
string uuid = 1; // `autogenpb:uuid:1524ed43-e57d-4bf9-9449-1cdfdc498605` string uuid = 1; // `autogenpb:uuid:1524ed43-e57d-4bf9-9449-1cdfdc498605`
string version = 2; // `autogenpb:version:v0.0.1` string version = 2; // `autogenpb:version:v0.0.1`
repeated HttpRequest HttpRequests = 3; // THIS MUST BE HttpRequest and then HttpRequests repeated HttpRequest HttpRequests = 3; // THIS MUST BE HttpRequest and then HttpRequests
} }

11
log.go Normal file
View File

@ -0,0 +1,11 @@
package httppb
import "fmt"
func (pb *HttpRequest) Log(a ...any) {
pb.Logs = append(pb.Logs, fmt.Sprint(a...))
}
func (pb *HttpRequest) Logf(s string, a ...any) {
pb.Logs = append(pb.Logs, fmt.Sprintf(s, a...))
}

72
middleware.go Normal file
View File

@ -0,0 +1,72 @@
package httppb
// http middleware example. probably not interesting since we only pass protobufs
// middleware concepts might, or might not be useful here
/*
// Define a key type to avoid context key collisions.
type contextKey string
const bufferedBodyKey = contextKey("bufferedBody")
// bufferBodyMiddleware reads the request body and replaces it with a new reader,
// allowing it to be read multiple times. The original body is stored in the request context.
func bufferBodyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Only buffer if there's a body to read.
if r.Body == nil || r.ContentLength == 0 {
next.ServeHTTP(w, r)
return
}
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading body in middleware: %v\n", err)
return
}
defer r.Body.Close()
// Store the buffered body in the context for downstream handlers.
ctx := context.WithValue(r.Context(), bufferedBodyKey, bodyBytes)
// Replace the original body with a new reader on the buffered bytes.
// This allows subsequent handlers to read the body again.
r.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
// Call the next handler in the chain with the modified request.
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// okHandler is the final handler. It can now safely access the body from the context,
// knowing that other middleware might have also read it.
func okHandler(w http.ResponseWriter, r *http.Request) {
// For demonstration, we can try reading the body directly here too.
// The middleware ensures this is a fresh stream of the buffered data.
bodyFromStream, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading body in handler: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
log.Printf("Handler read %d bytes from the request body stream.", len(bodyFromStream))
// We can also retrieve the body from the context if needed.
bodyFromContext, ok := r.Context().Value(bufferedBodyKey).([]byte)
if !ok {
log.Println("Could not retrieve buffered body from context.")
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
log.Printf("Handler retrieved %d bytes from context.", len(bodyFromContext))
// Prove they are the same.
if !bytes.Equal(bodyFromStream, bodyFromContext) {
log.Println("FATAL: Body from stream and context do not match!")
}
fmt.Fprintf(w, "Successfully read body of %d bytes.\n", len(bodyFromContext))
}
*/

52
post.go
View File

@ -9,8 +9,59 @@ import (
"net/url" "net/url"
"os" "os"
"os/user" "os/user"
"go.wit.com/log"
) )
// standard protobuf POST
// returns *HttpRequst protobuf
func DoPost(baseURL string, route string, data []byte) (*HttpRequest, error) {
// if you ever have 'http://www.wit.com//' GO will regect the server recieving it.
// Even though the linux kernel gets the network payload
// also it never gives you an error about that, it just goes away invisably inside GO
tmpURL, _ := url.Parse(baseURL) // "http://forge.grid.wit.com:2520")
finalURL := tmpURL.JoinPath(route) // Correctly produces ...:2520/patches
var err error
var req *http.Request
log.Info("httppb.HttpPost to", finalURL.String())
req, err = http.NewRequest(http.MethodPost, finalURL.String(), bytes.NewBuffer(data))
if req == nil {
return nil, err
}
username := os.Getenv("GIT_AUTHOR_NAME")
if username == "" {
usr, _ := user.Current()
username = usr.Username
}
req.Header.Set("author", username)
hostname, _ := os.Hostname()
req.Header.Set("hostname", hostname)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
reqPB, err := ReqToPB(req)
reqPB.URL = finalURL.String()
reqPB.ClientData = data
reqPB.ClientDataLen = int64(len(data))
reqPB.ServerData = body
reqPB.ServerDataLen = int64(len(body))
return reqPB, nil
}
func HttpPost(baseURL string, route string, data []byte) ([]byte, error) { func HttpPost(baseURL string, route string, data []byte) ([]byte, error) {
// Fix using url.JoinPath (Best Practice) // Fix using url.JoinPath (Best Practice)
tmpURL, _ := url.Parse(baseURL) // "http://forge.grid.wit.com:2520") tmpURL, _ := url.Parse(baseURL) // "http://forge.grid.wit.com:2520")
@ -19,6 +70,7 @@ func HttpPost(baseURL string, route string, data []byte) ([]byte, error) {
var err error var err error
var req *http.Request var req *http.Request
log.Info("httppb.HttpPost to", finalURL.String())
req, err = http.NewRequest(http.MethodPost, finalURL.String(), bytes.NewBuffer(data)) req, err = http.NewRequest(http.MethodPost, finalURL.String(), bytes.NewBuffer(data))
if req == nil { if req == nil {
return nil, err return nil, err

84
reqToPB.go Normal file
View File

@ -0,0 +1,84 @@
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
package httppb
import (
"io/ioutil"
"log"
"net"
"net/http"
"strings"
)
// remove '?' part and trailing '/'
func cleanURL(url string) string {
url = "/" + strings.Trim(url, "/")
return url
}
func getIpSimple(r *http.Request) string {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
log.Printf("could not split host port: %v", err)
return r.RemoteAddr // Fallback
}
return host
}
// getClientIP inspects the request for common headers to find the true client IP.
func getClientIP(r *http.Request) string {
// Caddy sets the X-Forwarded-For header.
if forwardedFor := r.Header.Get("X-Forwarded-For"); forwardedFor != "" {
// The header can be a comma-separated list of IPs. The first one is the original client.
ips := strings.Split(forwardedFor, ",")
return strings.TrimSpace(ips[0])
}
// Fallback to RemoteAddr if the header is not present.
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return r.RemoteAddr
}
return host
}
// converts an *http.Request to an http PB
func ReqToPB(r *http.Request) (*HttpRequest, error) {
// Convert the header map. http.Header is a map[string][]string.
// We'll just take the first value for each header for simplicity.
headers := make(map[string]string)
for name, values := range r.Header {
if len(values) > 0 {
headers[name] = strings.Join(values, "\n")
}
}
query := make(map[string]string)
for param, values := range r.URL.Query() {
if len(values) > 0 {
query[param] = strings.Join(values, "\n")
}
}
msg, err := ioutil.ReadAll(r.Body) // Read the body as []byte
r.Body.Close()
// log.Info("TRYING TO MARSHAL bytes:", len(msg), err)
pb := &HttpRequest{
Method: r.Method,
URL: r.URL.String(),
Proto: r.Proto,
Headers: headers,
IP: getClientIP(r),
Host: r.Host,
ClientData: msg,
ClientDataLen: int64(len(msg)),
Hostname: r.Header.Get("hostname"),
UserAgent: r.UserAgent(),
Query: query,
RemoteAddr: r.RemoteAddr,
}
pb.Route = cleanURL(r.URL.Path)
return pb, err
}

25
startHTTP.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
package httppb
import (
"fmt"
"net/http"
"go.wit.com/log"
)
// starts and sits waiting for HTTP requests
func StartHTTP(okHandler func(http.ResponseWriter, *http.Request), port int) error {
http.HandleFunc("/", okHandler)
p := fmt.Sprintf(":%d", port)
log.Println("Running on port", p)
err := http.ListenAndServe(p, nil)
if err != nil {
log.Println("Error starting server:", err)
return err
}
return nil
}

29
writefile.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright 1994-2025 WIT.COM Inc Licensed GPL 3.0
package httppb
import (
"embed"
"fmt"
"net/http"
"go.wit.com/log"
)
func WriteFile(w http.ResponseWriter, resfork embed.FS, filename string) error {
// fmt.Fprintln(w, "GOT TEST?")
fullname := "resources/" + filename
pfile, err := resfork.ReadFile(fullname)
if err != nil {
return err
}
var repohtml string
repohtml = string(pfile)
if filename == "goReference.svg" {
w.Header().Set("Content-Type", "image/svg+xml")
}
fmt.Fprintln(w, repohtml)
log.Println("writeFile() found internal file:", filename)
return nil
}