Server Sent Event (SSE) with Go

Source https://github.com/evgensr/sse

package main

import (
 "fmt"
 "net/http"
 "time"
)

func main() {
 http.HandleFunc("/events", eventsHandler)
 http.ListenAndServe(":8080", nil)
}

func eventsHandler(w http.ResponseWriter, r *http.Request) {
 // Set CORS headers to allow all origins. You may want to restrict this to specific origins in a production environment.
 w.Header().Set("Access-Control-Allow-Origin", "*")
 w.Header().Set("Access-Control-Expose-Headers", "Content-Type")

 w.Header().Set("Content-Type", "text/event-stream")
 w.Header().Set("Cache-Control", "no-cache")
 w.Header().Set("Connection", "keep-alive")

 // Simulate sending events (you can replace this with real data)
 for i := 0; i < 10; i++ {
 fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("Event %d", i))
 time.Sleep(2 * time.Second)
 w.(http.Flusher).Flush()
 }

 // Simulate closing the connection
 closeNotify := w.(http.CloseNotifier).CloseNotify()
 <-closeNotify
}

 

<!DOCTYPE html>
<html>
<head>
 <title>SSE Example</title>
</head>
<body>
 <div id="sse-data"></div>

 <script>
 const eventSource = new EventSource('http://localhost:8080/events');
 eventSource.onmessage = function(event) {
 const dataElement = document.getElementById('sse-data');
 dataElement.innerHTML += event.data + '<br>';
 };
 </script>
</body>
</html>

Оптимизация кода golang

Когда мы фильтруем фрагменты в Go, типичным решением является создание нового фрагмента для отфильтрованных элементов (var filtered []int).

Однако этот метод приводит к дополнительному выделению памяти, потому что мы создаём пустой слайс, и при каждом добавлении элемента нужно будет заново аллоцировать память.

Более разумным способом решения этой проблемы является такое создание filtered := numbers[:0]

Так мы создадим слайс с ёмкостью numbers и избегаем дополнительных выделений памяти в дальнейшем

В выражении numbers[:0], 0 указывает на индекс, до которого (не включая его) будут скопированы элементы из среза numbers. По сути, это создает новый срез, который имеет ту же емкость, что и исходный срез numbers, но не содержит никаких элементов.

Вот как это работает:

  • numbers[:] создает копию среза numbers, включая все его элементы.
  • numbers[:n], где n — это неотрицательное целое число, создает новый срез, который содержит первые n элементов numbers.
  • numbers[:0] создает новый срез, который не содержит элементов, но имеет ту же емкость, что и numbers. Это означает, что при добавлении элементов в срез filtered с помощью функции append память будет выделена из исходного массива, который лежит в основе среза numbers, до тех пор, пока не будет превышена его емкость.

Golang log

func logJson(data interface{}) string {
	bytes, err := json.Marshal(data)
	if err != nil {
		log.Fatal(err)
	}
	return string(bytes)
}


 

func logJson(data interface{}) {
    bytes, err := json.Marshal(data)
    if err != nil {
       log.Fatal(err)
    }
    log.Println(string(bytes))
}

Fixing For Loops in Go 1.22

Fixing For Loops in Go 1.22

https://go.dev/blog/loopvar-preview

David Chase и Russ Cox в официальном блоге Go рассказывают про проблему LoopVar (переменной цикла) и приводят конкретные примеры, в том числе из реального кейса проекта — Lets Encrypt.

Затем рассказывают, как именно эта проблема решается. Если кратко:

> change for loops to make these variables have per-iteration scope instead of per-loop scope

Как вы помните, эта фича уже есть в Go v1.21, но её нужно включить:

GOEXPERIMENT=loopvar go test

А в версии 1.22 она уже будет работать по-умолчанию

Получить курс доллара на forex


func forex() string {
	client := &http.Client{
		Timeout: 10 * time.Second,  
	}

 	req, err := http.NewRequest("GET", "https://www.investing.com/currencies/usd-rub", nil)
	if err != nil {
		log.Fatal(err)
	}

 	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537")

	res, err := client.Do(req)
	if err != nil {
		log.Println(err)
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		log.Printf("status code error: %d %s", res.StatusCode, res.Status)
	}

	// Load the HTML document
	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		log.Println(err)
	}

	var price string
	doc.Find("span[data-test='instrument-price-last']").Each(func(index int, item *goquery.Selection) {
		price = item.Text()
		fmt.Println(price)
	})

	return price
}

Работа со строками в golang



package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(    
        // true
        strings.Contains("test", "es"), 

        // 2
        strings.Count("test", "t"),

        // true
        strings.HasPrefix("test", "te"), 

        // true
        strings.HasSuffix("test", "st"), 

        // 1
        strings.Index("test", "e"), 

        // "a-b"
        strings.Join([]string{"a","b"}, "-"),

        // == "aaaaa"
        strings.Repeat("a", 5), 

        // "bbaa"
        strings.Replace("aaaa", "a", "b", 2),

        // []string{"a","b","c","d","e"}
        strings.Split("a-b-c-d-e", "-"), 

        // "test"
        strings.ToLower("TEST"), 

        // "TEST"
        strings.ToUpper("test"), 

    )
}