1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2025-06-16 17:43:13 +02:00

Add 'Cache: no' header, closes

This commit is contained in:
Philipp Heckel 2021-12-09 10:23:17 -05:00
parent d5be5d3e8c
commit d6fbccab55
11 changed files with 191 additions and 22 deletions

View file

@ -7,20 +7,35 @@ import (
type memCache struct {
messages map[string][]*message
nop bool
mu sync.Mutex
}
var _ cache = (*memCache)(nil)
// newMemCache creates an in-memory cache
func newMemCache() *memCache {
return &memCache{
messages: make(map[string][]*message),
nop: false,
}
}
// newNopCache creates an in-memory cache that discards all messages;
// it is always empty and can be used if caching is entirely disabled
func newNopCache() *memCache {
return &memCache{
messages: make(map[string][]*message),
nop: true,
}
}
func (s *memCache) AddMessage(m *message) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.nop {
return nil
}
if m.Event != messageEvent {
return errUnexpectedMessageType
}

View file

@ -1,6 +1,7 @@
package server
import (
"github.com/stretchr/testify/assert"
"testing"
)
@ -19,3 +20,16 @@ func TestMemCache_MessagesTagsPrioAndTitle(t *testing.T) {
func TestMemCache_Prune(t *testing.T) {
testCachePrune(t, newMemCache())
}
func TestMemCache_NopCache(t *testing.T) {
c := newNopCache()
assert.Nil(t, c.AddMessage(newDefaultMessage("mytopic", "my message")))
messages, err := c.Messages("mytopic", sinceAllMessages)
assert.Nil(t, err)
assert.Empty(t, messages)
topics, err := c.Topics()
assert.Nil(t, err)
assert.Empty(t, topics)
}

View file

@ -80,7 +80,7 @@
There are <a href="docs/publish/">more features</a> related to publishing messages: You can set a
<a href="docs/publish/#message-priority">notification priority</a>, a <a href="docs/publish/#message-title">title</a>,
and <a href="docs/publish/#tags-emojis">tag messages</a>.
Here's an example using all of them:
Here's an example using some of them together:
</p>
<code>
curl \<br/>
@ -203,7 +203,7 @@
Click the link to do so.
</p>
<p class="smallMarginBottom">
<b>Recent notifications</b> (cached for {{.CacheDuration}}):
<b>Recent notifications</b> ({{if .CacheDuration}}cached for {{.CacheDuration | durationToHuman}}{{else}}caching is disabled{{end}}):
</p>
<p id="detailNoNotifications">
<i>You haven't received any notifications for this topic yet.</i>

View file

@ -49,7 +49,7 @@ func (e errHTTP) Error() string {
type indexPage struct {
Topic string
CacheDuration string
CacheDuration time.Duration
}
type sinceTime time.Time
@ -85,9 +85,13 @@ var (
docsRegex = regexp.MustCompile(`^/docs(|/.*)$`)
disallowedTopics = []string{"docs", "static"}
templateFnMap = template.FuncMap{
"durationToHuman": util.DurationToHuman,
}
//go:embed "index.gohtml"
indexSource string
indexTemplate = template.Must(template.New("index").Parse(indexSource))
indexTemplate = template.Must(template.New("index").Funcs(templateFnMap).Parse(indexSource))
//go:embed "example.html"
exampleSource string
@ -139,7 +143,9 @@ func New(conf *config.Config) (*Server, error) {
}
func createCache(conf *config.Config) (cache, error) {
if conf.CacheFile != "" {
if conf.CacheDuration == 0 {
return newNopCache(), nil
} else if conf.CacheFile != "" {
return newSqliteCache(conf.CacheFile)
}
return newMemCache(), nil
@ -241,7 +247,7 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request) error {
func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) error {
return indexTemplate.Execute(w, &indexPage{
Topic: r.URL.Path[1:],
CacheDuration: util.DurationToHuman(s.config.CacheDuration),
CacheDuration: s.config.CacheDuration,
})
}
@ -278,15 +284,17 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, _ *visito
if m.Message == "" {
return errHTTPBadRequest
}
title, priority, tags := parseHeaders(r.Header)
title, priority, tags, cache := parseHeaders(r.Header)
m.Title = title
m.Priority = priority
m.Tags = tags
if err := t.Publish(m); err != nil {
return err
}
if err := s.cache.AddMessage(m); err != nil {
return err
if cache {
if err := s.cache.AddMessage(m); err != nil {
return err
}
}
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
if err := json.NewEncoder(w).Encode(m); err != nil {
@ -298,7 +306,7 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, _ *visito
return nil
}
func parseHeaders(header http.Header) (title string, priority int, tags []string) {
func parseHeaders(header http.Header) (title string, priority int, tags []string, cache bool) {
title = readHeader(header, "x-title", "title", "ti", "t")
priorityStr := readHeader(header, "x-priority", "priority", "prio", "p")
if priorityStr != "" {
@ -324,7 +332,8 @@ func parseHeaders(header http.Header) (title string, priority int, tags []string
tags = append(tags, strings.TrimSpace(s))
}
}
return title, priority, tags
cache = readHeader(header, "x-cache", "cache") != "no"
return title, priority, tags, cache
}
func readHeader(header http.Header, names ...string) string {

View file

@ -150,7 +150,74 @@ func TestServer_StaticSites(t *testing.T) {
assert.Equal(t, 200, rr.Code)
assert.Contains(t, rr.Body.String(), `Made with ❤️ by Philipp C. Heckel`)
assert.Contains(t, rr.Body.String(), `<script src=static/js/extra.js></script>`)
}
func TestServer_PublishNoCache(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic", "this message is not cached", map[string]string{
"Cache": "no",
})
msg := toMessage(t, response.Body.String())
assert.NotEmpty(t, msg.ID)
assert.Equal(t, "this message is not cached", msg.Message)
response = request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
messages := toMessages(t, response.Body.String())
assert.Empty(t, messages)
}
func TestServer_PublishAndMultiPoll(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "PUT", "/mytopic1", "message 1", nil)
msg := toMessage(t, response.Body.String())
assert.NotEmpty(t, msg.ID)
assert.Equal(t, "mytopic1", msg.Topic)
assert.Equal(t, "message 1", msg.Message)
response = request(t, s, "PUT", "/mytopic2", "message 2", nil)
msg = toMessage(t, response.Body.String())
assert.NotEmpty(t, msg.ID)
assert.Equal(t, "mytopic2", msg.Topic)
assert.Equal(t, "message 2", msg.Message)
response = request(t, s, "GET", "/mytopic1/json?poll=1", "", nil)
messages := toMessages(t, response.Body.String())
assert.Equal(t, 1, len(messages))
assert.Equal(t, "mytopic1", messages[0].Topic)
assert.Equal(t, "message 1", messages[0].Message)
response = request(t, s, "GET", "/mytopic1,mytopic2/json?poll=1", "", nil)
messages = toMessages(t, response.Body.String())
assert.Equal(t, 2, len(messages))
assert.Equal(t, "mytopic1", messages[0].Topic)
assert.Equal(t, "message 1", messages[0].Message)
assert.Equal(t, "mytopic2", messages[1].Topic)
assert.Equal(t, "message 2", messages[1].Message)
}
func TestServer_PublishWithNopCache(t *testing.T) {
c := newTestConfig(t)
c.CacheDuration = 0
s := newTestServer(t, c)
subscribeRR := httptest.NewRecorder()
subscribeCancel := subscribe(t, s, "/mytopic/json", subscribeRR)
publishRR := request(t, s, "PUT", "/mytopic", "my first message", nil)
assert.Equal(t, 200, publishRR.Code)
subscribeCancel()
messages := toMessages(t, subscribeRR.Body.String())
assert.Equal(t, 2, len(messages))
assert.Equal(t, openEvent, messages[0].Event)
assert.Equal(t, messageEvent, messages[1].Event)
assert.Equal(t, "my first message", messages[1].Message)
response := request(t, s, "GET", "/mytopic/json?poll=1", "", nil)
messages = toMessages(t, response.Body.String())
assert.Empty(t, messages)
}
func newTestConfig(t *testing.T) *config.Config {

View file

@ -470,11 +470,19 @@ li {
margin-bottom: 20px;
}
#detail .detailDate {
margin-bottom: 2px;
}
#detail .detailDate, #detail .detailTags {
color: #888;
font-size: 0.9em;
}
#detail .detailTags {
margin-top: 2px;
}
#detail .detailDate img {
width: 20px;
height: 20px;
@ -483,11 +491,6 @@ li {
#detail .detailTitle {
font-weight: bold;
font-size: 1.1em;
}
#detail .detailMessage {
font-size: 1.1em;
}
#detail #detailMain {

View file

@ -294,7 +294,7 @@ const formatTitle = (m) => {
const formatTitleA = (m) => {
const emojiList = toEmojis(m.tags);
if (emojiList) {
if (emojiList.length > 0) {
return `${emojiList.join(" ")} ${m.title}`;
} else {
return m.title;
@ -306,7 +306,7 @@ const formatMessage = (m) => {
return m.message;
} else {
const emojiList = toEmojis(m.tags);
if (emojiList) {
if (emojiList.length > 0) {
return `${emojiList.join(" ")} ${m.message}`;
} else {
return m.message;