Files
hostname-service/hostname_service.go
T
2025-05-21 13:00:48 -05:00

158 lines
3.4 KiB
Go

package main
import (
"encoding/json"
"io"
"log"
"net"
"net/http"
"os"
"github.com/kardianos/service"
)
const (
port = "8999"
logFile = "hostname_service.log"
logFlags = log.Ldate | log.Ltime | log.Lshortfile
)
var logger *log.Logger
// Request structure
type Request struct {
IP string `json:"ip"`
}
// Response structure
type Response struct {
Hostname string `json:"hostname,omitempty"`
Error string `json:"error,omitempty"`
}
type program struct {
server *http.Server
}
func (p *program) Start(s service.Service) error {
// Start should not block
go p.run()
return nil
}
func (p *program) run() {
mux := http.NewServeMux()
mux.HandleFunc("/", handleRequest)
p.server = &http.Server{
Addr: ":" + port,
Handler: mux,
}
logger.Println("Starting server on port", port)
err := p.server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
logger.Fatalf("Server failed: %v", err)
}
}
func (p *program) Stop(s service.Service) error {
logger.Println("Stopping server...")
if p.server != nil {
return p.server.Close()
}
return nil
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
logger.Printf("Rejected %s request from %s", r.Method, r.RemoteAddr)
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
logger.Printf("Failed to read request: %v", err)
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
defer r.Body.Close()
var req Request
err = json.Unmarshal(body, &req)
if err != nil || req.IP == "" {
logger.Printf("Invalid JSON or missing IP: %s", string(body))
respond(w, Response{Error: "Invalid JSON or missing 'ip'"})
return
}
logger.Printf("Received IP lookup request: %s", req.IP)
names, err := net.LookupAddr(req.IP)
if err != nil || len(names) == 0 {
logger.Printf("Hostname not found for IP: %s", req.IP)
respond(w, Response{Error: "Hostname not found"})
return
}
hostname := names[0]
logger.Printf("Resolved %s to %s", req.IP, hostname)
respond(w, Response{Hostname: hostname})
}
func respond(w http.ResponseWriter, resp Response) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func setupLogging() {
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Unable to open log file: %v", err)
}
logger = log.New(file, "", logFlags)
}
func main() {
setupLogging()
svcConfig := &service.Config{
Name: "HostnameGoHTTPService",
DisplayName: "Hostname Go HTTP Resolver Service",
Description: "Resolves hostnames from IPs via POST JSON requests on port 8999.",
}
prg := &program{}
s, err := service.New(prg, svcConfig)
if err != nil {
logger.Fatalf("Failed to create service: %v", err)
}
// Logging via service (or fallback)
errs := make(chan error, 5)
loggerAdapter, err := s.Logger(errs)
if err == nil {
logger.SetOutput(serviceLogWriter{loggerAdapter})
}
if len(os.Args) > 1 && os.Args[1] == "run" {
// Standalone mode for testing
prg.run()
} else {
err = s.Run()
if err != nil {
logger.Fatalf("Service failed: %v", err)
}
}
}
// Adapter to redirect standard logger to service log
type serviceLogWriter struct {
logger service.Logger
}
func (w serviceLogWriter) Write(p []byte) (n int, err error) {
w.logger.Info(string(p))
return len(p), nil
}