Repository: mmcdole/gofeed
Pull request #123 (2019-07-03 21:08:13)

Context support is added for ParseURL
`ParseURLWithContext` method is added to support cancel or timing out for http requests.


Patch:
diff --git a/.gitignore b/.gitignore
index 0dee695..1340f5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,6 @@ _testmain.go
 .DS_STORE
 
 cmd/ftest/ftest
+
+# Goland specific files
+.idea
diff --git a/extensions/itunes.go b/extensions/itunes.go
index 4cb76da..d6bdff2 100644
--- a/extensions/itunes.go
+++ b/extensions/itunes.go
@@ -29,7 +29,10 @@ type ITunesItemExtension struct {
 	Summary           string `json:"summary,omitempty"`
 	Image             string `json:"image,omitempty"`
 	IsClosedCaptioned string `json:"isClosedCaptioned,omitempty"`
+	Episode           string `json:"episode,omitempty"`
+	Season            string `json:"season,omitempty"`
 	Order             string `json:"order,omitempty"`
+	EpisodeType       string `json:"episodeType,omitempty"`
 }
 
 // ITunesCategory is a category element for itunes feeds.
@@ -76,7 +79,10 @@ func NewITunesItemExtension(extensions map[string][]Extension) *ITunesItemExtens
 	entry.Keywords = parseTextExtension("keywords", extensions)
 	entry.Image = parseImage(extensions)
 	entry.IsClosedCaptioned = parseTextExtension("isClosedCaptioned", extensions)
+	entry.Episode = parseTextExtension("episode", extensions)
+	entry.Season = parseTextExtension("season", extensions)
 	entry.Order = parseTextExtension("order", extensions)
+	entry.EpisodeType = parseTextExtension("episodeType", extensions)
 	return entry
 }
 
diff --git a/parser.go b/parser.go
index 9a71239..273b466 100644
--- a/parser.go
+++ b/parser.go
@@ -2,6 +2,7 @@ package gofeed
 
 import (
 	"bytes"
+	"context"
 	"errors"
 	"fmt"
 	"io"
@@ -76,12 +77,20 @@ func (f *Parser) Parse(feed io.Reader) (*Feed, error) {
 // ParseURL fetches the contents of a given url and
 // attempts to parse the response into the universal feed type.
 func (f *Parser) ParseURL(feedURL string) (feed *Feed, err error) {
+	return f.ParseURLWithContext(feedURL, context.Background())
+}
+
+// ParseURLWithContext fetches contents of a given url and
+// attempts to parse the response into the universal feed type.
+// Request could be canceled or timeout via given context
+func (f *Parser) ParseURLWithContext(feedURL string, ctx context.Context) (feed *Feed, err error) {
 	client := f.httpClient()
 
 	req, err := http.NewRequest("GET", feedURL, nil)
 	if err != nil {
 		return nil, err
 	}
+	req = req.WithContext(ctx)
 	req.Header.Set("User-Agent", "Gofeed/1.0")
 	resp, err := client.Do(req)
 


Test patch:
diff --git a/parser_test.go b/parser_test.go
index d44e665..d7d671f 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -2,6 +2,7 @@ package gofeed_test
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -10,6 +11,7 @@ import (
 	"net/url"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/mmcdole/gofeed"
 	"github.com/stretchr/testify/assert"
@@ -113,7 +115,7 @@ func TestParser_ParseURL_Success(t *testing.T) {
 		f, _ := ioutil.ReadFile(path)
 
 		// Get actual value
-		server, client := mockServerResponse(200, string(f))
+		server, client := mockServerResponse(200, string(f), 0)
 		fp := gofeed.NewParser()
 		fp.Client = client
 		feed, err := fp.ParseURL(server.URL)
@@ -130,8 +132,18 @@ func TestParser_ParseURL_Success(t *testing.T) {
 	}
 }
 
+func TestParser_ParseURLWithContext(t *testing.T) {
+	server, client := mockServerResponse(404, "", 1*time.Minute)
+	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
+	defer cancel()
+	fp := gofeed.NewParser()
+	fp.Client = client
+	_, err := fp.ParseURLWithContext(server.URL, ctx)
+	assert.True(t, strings.Contains(err.Error(), ctx.Err().Error()))
+}
+
 func TestParser_ParseURL_Failure(t *testing.T) {
-	server, client := mockServerResponse(404, "")
+	server, client := mockServerResponse(404, "", 0)
 	fp := gofeed.NewParser()
 	fp.Client = client
 	feed, err := fp.ParseURL(server.URL)
@@ -143,8 +155,9 @@ func TestParser_ParseURL_Failure(t *testing.T) {
 
 // Test Helpers
 
-func mockServerResponse(code int, body string) (*httptest.Server, *http.Client) {
+func mockServerResponse(code int, body string, delay time.Duration) (*httptest.Server, *http.Client) {
 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		time.Sleep(delay)
 		w.WriteHeader(code)
 		w.Header().Set("Content-Type", "application/xml")
 		io.WriteString(w, body)