package proxy import ( "net/http" "sync" "time" ) // Entry — одна перехваченная транзакция запрос/ответ, готовая к показу // в TUI и (по Raw) к загрузке прямо в Request Builder. type Entry struct { ID int Timestamp time.Time Method string URL string Raw string // сырой HTTP-запрос целиком — то, что вставляется в §маркеры§ форму StatusCode int Length int64 Headers http.Header // заголовки ответа — для цветовой маркировки по Content-Type в TUI ResponseBody string // тело ответа, обрезанное до maxResponseCapture байт — для полнотекстового поиска в Project Err error // не nil, если запрос не удалось выполнить (таймаут, обрыв соединения и т.п.) } // Store хранит историю перехваченных запросов и рассылает новые записи // подписчикам (обычно единственный подписчик — TUI) через каналы. type Store struct { mu sync.Mutex entries []Entry nextID int subs []chan Entry } // NewStore создаёт пустое хранилище. func NewStore() *Store { return &Store{} } // Add сохраняет запись, присваивает ей ID и рассылает подписчикам. // Рассылка неблокирующая: если подписчик не успевает вычитывать канал, // запись всё равно остаётся в общей истории (доступна через All), // прокси-сервер из-за медленного TUI не подвиснет. func (s *Store) Add(e Entry) Entry { s.mu.Lock() s.nextID++ e.ID = s.nextID s.entries = append(s.entries, e) subs := append([]chan Entry(nil), s.subs...) s.mu.Unlock() for _, ch := range subs { select { case ch <- e: default: } } return e } // All возвращает копию текущей истории — используется для первичной // загрузки TUI при старте, если прокси уже что-то успел перехватить. func (s *Store) All() []Entry { s.mu.Lock() defer s.mu.Unlock() out := make([]Entry, len(s.entries)) copy(out, s.entries) return out } // Subscribe возвращает канал, в который будут падать все новые записи, // добавленные после момента подписки. func (s *Store) Subscribe() <-chan Entry { ch := make(chan Entry, 64) s.mu.Lock() s.subs = append(s.subs, ch) s.mu.Unlock() return ch }