├── report.go ├── .gitattributes ├── .gitignore ├── message.go ├── option.go ├── audience.go ├── payload.go ├── platform.go ├── schedule.go ├── notice.go ├── httpclient.go ├── README.md ├── pushclient.go └── httplib.go /report.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | type Message struct { 4 | Content string `json:"msg_content"` 5 | Title string `json:"title,omitempty"` 6 | ContentType string `json:"content_type,omitempty"` 7 | Extras map[string]interface{} `json:"extras,omitempty"` 8 | } 9 | 10 | func (this *Message) SetContent(c string) { 11 | this.Content = c 12 | 13 | } 14 | 15 | func (this *Message) SetTitle(title string) { 16 | this.Title = title 17 | } 18 | 19 | func (this *Message) SetContentType(t string) { 20 | this.ContentType = t 21 | } 22 | 23 | func (this *Message) AddExtras(key string, value interface{}) { 24 | if this.Extras == nil { 25 | this.Extras = make(map[string]interface{}) 26 | } 27 | this.Extras[key] = value 28 | } 29 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | type Option struct { 4 | SendNo int `json:"sendno,omitempty"` 5 | TimeLive int `json:"time_to_live,omitempty"` 6 | ApnsProduction bool `json:"apns_production"` 7 | OverrideMsgId int64 `json:"override_msg_id,omitempty"` 8 | BigPushDuration int `json:"big_push_duration,omitempty"` 9 | } 10 | 11 | func (this *Option) SetSendno(no int) { 12 | this.SendNo = no 13 | } 14 | 15 | func (this *Option) SetTimelive(timelive int) { 16 | this.TimeLive = timelive 17 | } 18 | 19 | func (this *Option) SetOverrideMsgId(id int64) { 20 | this.OverrideMsgId = id 21 | } 22 | 23 | func (this *Option) SetApns(apns bool) { 24 | this.ApnsProduction = apns 25 | } 26 | 27 | func (this *Option) SetBigPushDuration(bigPushDuration int) { 28 | this.BigPushDuration = bigPushDuration 29 | } 30 | -------------------------------------------------------------------------------- /audience.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | const ( 4 | TAG = "tag" 5 | TAG_AND = "tag_and" 6 | ALIAS = "alias" 7 | ID = "registration_id" 8 | ) 9 | 10 | type Audience struct { 11 | Object interface{} 12 | audience map[string][]string 13 | } 14 | 15 | func (this *Audience) All() { 16 | this.Object = "all" 17 | } 18 | 19 | func (this *Audience) SetID(ids []string) { 20 | this.set(ID, ids) 21 | } 22 | 23 | func (this *Audience) SetTag(tags []string) { 24 | this.set(TAG, tags) 25 | } 26 | 27 | func (this *Audience) SetTagAnd(tags []string) { 28 | this.set(TAG_AND, tags) 29 | } 30 | 31 | func (this *Audience) SetAlias(alias []string) { 32 | this.set(ALIAS, alias) 33 | } 34 | 35 | func (this *Audience) set(key string, v []string) { 36 | if this.audience == nil { 37 | this.audience = make(map[string][]string) 38 | this.Object = this.audience 39 | } 40 | 41 | //v, ok = this.audience[key] 42 | //if ok { 43 | // return 44 | //} 45 | this.audience[key] = v 46 | } 47 | -------------------------------------------------------------------------------- /payload.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type PayLoad struct { 8 | Platform interface{} `json:"platform"` 9 | Audience interface{} `json:"audience"` 10 | Notification interface{} `json:"notification,omitempty"` 11 | Message interface{} `json:"message,omitempty"` 12 | Options *Option `json:"options,omitempty"` 13 | } 14 | 15 | func NewPushPayLoad() *PayLoad { 16 | pl := &PayLoad{} 17 | o := &Option{} 18 | o.ApnsProduction = false 19 | pl.Options = o 20 | return pl 21 | } 22 | 23 | func (this *PayLoad) SetPlatform(pf *Platform) { 24 | this.Platform = pf.Os 25 | } 26 | 27 | func (this *PayLoad) SetAudience(ad *Audience) { 28 | this.Audience = ad.Object 29 | } 30 | 31 | func (this *PayLoad) SetOptions(o *Option) { 32 | this.Options = o 33 | } 34 | 35 | func (this *PayLoad) SetMessage(m *Message) { 36 | this.Message = m 37 | } 38 | 39 | func (this *PayLoad) SetNotice(notice *Notice) { 40 | this.Notification = notice 41 | } 42 | 43 | func (this *PayLoad) ToBytes() ([]byte, error) { 44 | content, err := json.Marshal(this) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return content, nil 49 | } 50 | -------------------------------------------------------------------------------- /platform.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | const ( 8 | IOS = "ios" 9 | ANDROID = "android" 10 | WINPHONE = "winphone" 11 | ) 12 | 13 | type Platform struct { 14 | Os interface{} 15 | osArry []string 16 | } 17 | 18 | func (this *Platform) All() { 19 | this.Os = "all" 20 | } 21 | 22 | func (this *Platform) Add(os string) error { 23 | if this.Os == nil { 24 | this.osArry = make([]string, 0, 3) 25 | } else { 26 | switch this.Os.(type) { 27 | case string: 28 | return errors.New("platform is all") 29 | default: 30 | } 31 | } 32 | 33 | //判断是否重复 34 | for _, value := range this.osArry { 35 | if os == value { 36 | return nil 37 | } 38 | } 39 | 40 | switch os { 41 | case IOS: 42 | fallthrough 43 | case ANDROID: 44 | fallthrough 45 | case WINPHONE: 46 | this.osArry = append(this.osArry, os) 47 | this.Os = this.osArry 48 | default: 49 | return errors.New("unknow platform") 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func (this *Platform) AddIOS() { 56 | this.Add(IOS) 57 | } 58 | 59 | func (this *Platform) AddAndrid() { 60 | this.Add(ANDROID) 61 | } 62 | 63 | func (this *Platform) AddWinphone() { 64 | this.Add(WINPHONE) 65 | } 66 | -------------------------------------------------------------------------------- /schedule.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | type Schedule struct { 9 | Cid string `json:"cid"` 10 | Name string `json:"name"` 11 | Enabled bool `json:"enabled"` 12 | Trigger map[string]interface{} `json:"trigger"` 13 | Push *PayLoad `json:"push"` 14 | } 15 | 16 | func NewSchedule(name,cid string, enabled bool, push *PayLoad) *Schedule { 17 | return &Schedule{ 18 | Cid: cid, 19 | Name: name, 20 | Enabled: enabled, 21 | Push: push, 22 | } 23 | } 24 | func (s *Schedule) SingleTrigger(t time.Time) { 25 | s.Trigger = map[string]interface{}{ 26 | "single": map[string]interface{}{ 27 | "time": t.Format("2006-01-02 15:04:05"), 28 | }, 29 | } 30 | 31 | } 32 | 33 | func (s *Schedule) PeriodicalTrigger(start time.Time, end time.Time, time time.Time, timeUnit string, frequency int, point []string) { 34 | s.Trigger = map[string]interface{}{ 35 | "periodical": map[string]interface{}{ 36 | "start": start.Format("2006-01-02 15:04:05"), 37 | "end": end.Format("2006-01-02 15:04:05"), 38 | "time": start.Format("15:04:05"), 39 | "time_unit": timeUnit, 40 | "frequency": frequency, 41 | "point": point, 42 | }, 43 | } 44 | } 45 | func (this *Schedule) ToBytes() ([]byte, error) { 46 | content, err := json.Marshal(this) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return content, nil 51 | } 52 | -------------------------------------------------------------------------------- /notice.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | type Notice struct { 4 | Alert string `json:"alert,omitempty"` 5 | Android *AndroidNotice `json:"android,omitempty"` 6 | IOS *IOSNotice `json:"ios,omitempty"` 7 | WINPhone *WinPhoneNotice `json:"winphone,omitempty"` 8 | } 9 | 10 | type AndroidNotice struct { 11 | Alert string `json:"alert"` 12 | Title string `json:"title,omitempty"` 13 | BuilderId int `json:"builder_id,omitempty"` 14 | Extras map[string]interface{} `json:"extras,omitempty"` 15 | } 16 | 17 | type IOSNotice struct { 18 | Alert interface{} `json:"alert"` 19 | Sound string `json:"sound,omitempty"` 20 | Badge string `json:"badge,omitempty"` 21 | ContentAvailable bool `json:"content-available,omitempty"` 22 | MutableContent bool `json:"mutable-content,omitempty"` 23 | Category string `json:"category,omitempty"` 24 | Extras map[string]interface{} `json:"extras,omitempty"` 25 | } 26 | 27 | type WinPhoneNotice struct { 28 | Alert string `json:"alert"` 29 | Title string `json:"title,omitempty"` 30 | OpenPage string `json:"_open_page,omitempty"` 31 | Extras map[string]interface{} `json:"extras,omitempty"` 32 | } 33 | 34 | func (this *Notice) SetAlert(alert string) { 35 | this.Alert = alert 36 | } 37 | 38 | func (this *Notice) SetAndroidNotice(n *AndroidNotice) { 39 | this.Android = n 40 | } 41 | 42 | func (this *Notice) SetIOSNotice(n *IOSNotice) { 43 | this.IOS = n 44 | } 45 | 46 | func (this *Notice) SetWinPhoneNotice(n *WinPhoneNotice) { 47 | this.WINPhone = n 48 | } 49 | -------------------------------------------------------------------------------- /httpclient.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | const ( 11 | CHARSET = "UTF-8" 12 | CONTENT_TYPE_JSON = "application/json" 13 | DEFAULT_CONNECTION_TIMEOUT = 20 //seconds 14 | DEFAULT_SOCKET_TIMEOUT = 30 // seconds 15 | ) 16 | 17 | func SendPostString(url, content, authCode string) (string, error) { 18 | 19 | //req := Post(url).Debug(true) 20 | req := Post(url) 21 | req.SetTimeout(DEFAULT_CONNECTION_TIMEOUT*time.Second, DEFAULT_SOCKET_TIMEOUT*time.Second) 22 | req.Header("Connection", "Keep-Alive") 23 | req.Header("Charset", CHARSET) 24 | req.Header("Authorization", authCode) 25 | req.Header("Content-Type", CONTENT_TYPE_JSON) 26 | req.SetProtocolVersion("HTTP/1.1") 27 | req.Body(content) 28 | 29 | return req.String() 30 | } 31 | 32 | func SendPostBytes(url string, content []byte, authCode string) (string, error) { 33 | 34 | req := Post(url) 35 | req.SetTimeout(DEFAULT_CONNECTION_TIMEOUT*time.Second, DEFAULT_SOCKET_TIMEOUT*time.Second) 36 | req.Header("Connection", "Keep-Alive") 37 | req.Header("Charset", CHARSET) 38 | req.Header("Authorization", authCode) 39 | req.Header("Content-Type", CONTENT_TYPE_JSON) 40 | req.SetProtocolVersion("HTTP/1.1") 41 | req.Body(content) 42 | 43 | return req.String() 44 | } 45 | 46 | func SendPostBytes2(url string, data []byte, authCode string) (string, error) { 47 | 48 | client := &http.Client{} 49 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) 50 | req.Header.Add("Charset", CHARSET) 51 | req.Header.Add("Authorization", authCode) 52 | req.Header.Add("Content-Type", CONTENT_TYPE_JSON) 53 | resp, err := client.Do(req) 54 | 55 | if err != nil { 56 | if resp != nil { 57 | resp.Body.Close() 58 | } 59 | return "", err 60 | } 61 | if resp == nil { 62 | return "", nil 63 | } 64 | 65 | defer resp.Body.Close() 66 | r, err := ioutil.ReadAll(resp.Body) 67 | if err != nil { 68 | return "", err 69 | } 70 | return string(r), nil 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jpush-api-go-client 2 | =================== 3 | 4 | 概述 5 | ----------------------------------- 6 | 这是JPush REST API 的 go 版本封装开发包,仅支持最新的REST API v3功能。 7 | REST API 文档:http://docs.jpush.cn/display/dev/Push-API-v3 8 | 9 | 10 | 使用 11 | ----------------------------------- 12 | go get github.com/ylywyn/jpush-api-go-client 13 | 14 | 15 | 推送流程 16 | ----------------------------------- 17 | ### 1.构建要推送的平台: jpushclient.Platform 18 | //Platform 19 | var pf jpushclient.Platform 20 | pf.Add(jpushclient.ANDROID) 21 | pf.Add(jpushclient.IOS) 22 | pf.Add(jpushclient.WINPHONE) 23 | //pf.All() 24 | 25 | ### 2.构建接收听众: jpushclient.Audience 26 | //Audience 27 | var ad jpushclient.Audience 28 | s := []string{"t1", "t2", "t3"} 29 | ad.SetTag(s) 30 | id := []string{"1", "2", "3"} 31 | ad.SetID(id) 32 | //ad.All() 33 | 34 | ### 3.构建通知 jpushclient.Notice,或者消息: jpushclient.Message 35 | 36 | //Notice 37 | var notice jpushclient.Notice 38 | notice.SetAlert("alert_test") 39 | notice.SetAndroidNotice(&jpushclient.AndroidNotice{Alert: "AndroidNotice"}) 40 | notice.SetIOSNotice(&jpushclient.IOSNotice{Alert: "IOSNotice"}) 41 | notice.SetWinPhoneNotice(&jpushclient.WinPhoneNotice{Alert: "WinPhoneNotice"}) 42 | 43 | //jpushclient.Message 44 | var msg jpushclient.Message 45 | msg.Title = "Hello" 46 | msg.Content = "你是ylywn" 47 | 48 | ### 4.构建jpushclient.PayLoad 49 | payload := jpushclient.NewPushPayLoad() 50 | payload.SetPlatform(&pf) 51 | payload.SetAudience(&ad) 52 | payload.SetMessage(&msg) 53 | payload.SetNotice(¬ice) 54 | 55 | 56 | ### 5.构建PushClient,发出推送 57 | c := jpushclient.NewPushClient(secret, appKey) 58 | r, err := c.Send(bytes) 59 | if err != nil { 60 | fmt.Printf("err:%s", err.Error()) 61 | } else { 62 | fmt.Printf("ok:%s", r) 63 | } 64 | 65 | 66 | ### 6.完整demo 67 | package main 68 | 69 | import ( 70 | "fmt" 71 | "github.com/ylywyn/jpush-api-go-client" 72 | ) 73 | 74 | const ( 75 | appKey = "you jpush appkey" 76 | secret = "you jpush secret" 77 | ) 78 | 79 | func main() { 80 | 81 | //Platform 82 | var pf jpushclient.Platform 83 | pf.Add(jpushclient.ANDROID) 84 | pf.Add(jpushclient.IOS) 85 | pf.Add(jpushclient.WINPHONE) 86 | //pf.All() 87 | 88 | //Audience 89 | var ad jpushclient.Audience 90 | s := []string{"1", "2", "3"} 91 | ad.SetTag(s) 92 | ad.SetAlias(s) 93 | ad.SetID(s) 94 | //ad.All() 95 | 96 | //Notice 97 | var notice jpushclient.Notice 98 | notice.SetAlert("alert_test") 99 | notice.SetAndroidNotice(&jpushclient.AndroidNotice{Alert: "AndroidNotice"}) 100 | notice.SetIOSNotice(&jpushclient.IOSNotice{Alert: "IOSNotice"}) 101 | notice.SetWinPhoneNotice(&jpushclient.WinPhoneNotice{Alert: "WinPhoneNotice"}) 102 | 103 | var msg jpushclient.Message 104 | msg.Title = "Hello" 105 | msg.Content = "你是ylywn" 106 | 107 | payload := jpushclient.NewPushPayLoad() 108 | payload.SetPlatform(&pf) 109 | payload.SetAudience(&ad) 110 | payload.SetMessage(&msg) 111 | payload.SetNotice(¬ice) 112 | 113 | bytes, _ := payload.ToBytes() 114 | fmt.Printf("%s\r\n", string(bytes)) 115 | 116 | //push 117 | c := jpushclient.NewPushClient(secret, appKey) 118 | str, err := c.Send(bytes) 119 | if err != nil { 120 | fmt.Printf("err:%s", err.Error()) 121 | } else { 122 | fmt.Printf("ok:%s", str) 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /pushclient.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | SUCCESS_FLAG = "msg_id" 13 | HOST_NAME_SSL = "https://api.jpush.cn/v3/push" 14 | HOST_SCHEDULE = "https://api.jpush.cn/v3/schedules" 15 | HOST_REPORT = "https://report.jpush.cn/v3/received" 16 | BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 17 | ) 18 | 19 | var base64Coder = base64.NewEncoding(BASE64_TABLE) 20 | 21 | type PushClient struct { 22 | MasterSecret string 23 | AppKey string 24 | AuthCode string 25 | BaseUrl string 26 | } 27 | 28 | func NewPushClient(secret, appKey string) *PushClient { 29 | //base64 30 | auth := "Basic " + base64Coder.EncodeToString([]byte(appKey+":"+secret)) 31 | pusher := &PushClient{secret, appKey, auth, HOST_NAME_SSL} 32 | return pusher 33 | } 34 | 35 | func (this *PushClient) Send(data []byte) (string, error) { 36 | return this.SendPushBytes(data) 37 | } 38 | func (this *PushClient) CreateSchedule(data []byte) (string, error) { 39 | // this.BaseUrl = HOST_SCHEDULE 40 | return this.SendScheduleBytes(data, HOST_SCHEDULE) 41 | } 42 | func (this *PushClient) DeleteSchedule(id string) (string, error) { 43 | // this.BaseUrl = HOST_SCHEDULE 44 | return this.SendDeleteScheduleRequest(id, HOST_SCHEDULE) 45 | } 46 | func (this *PushClient) GetSchedule(id string) (string, error) { 47 | // GET https://api.jpush.cn/v3/schedules/{schedule_id} 48 | // this.BaseUrl = HOST_SCHEDULE 49 | return this.SendGetScheduleRequest(id, HOST_SCHEDULE) 50 | 51 | } 52 | func (this *PushClient) GetReport(msg_ids string) (string, error) { 53 | // this.BaseUrl = HOST_REPORT 54 | return this.SendGetReportRequest(msg_ids, HOST_REPORT) 55 | } 56 | func (this *PushClient) SendPushString(content string) (string, error) { 57 | ret, err := SendPostString(this.BaseUrl, content, this.AuthCode) 58 | if err != nil { 59 | return ret, err 60 | } 61 | if strings.Contains(ret, "msg_id") { 62 | return ret, nil 63 | } else { 64 | return "", errors.New(ret) 65 | } 66 | } 67 | 68 | func (this *PushClient) SendPushBytes(content []byte) (string, error) { 69 | //ret, err := SendPostBytes(this.BaseUrl, content, this.AuthCode) 70 | ret, err := SendPostBytes2(this.BaseUrl, content, this.AuthCode) 71 | if err != nil { 72 | return ret, err 73 | } 74 | if strings.Contains(ret, "msg_id") { 75 | return ret, nil 76 | } else { 77 | return "", errors.New(ret) 78 | } 79 | } 80 | 81 | func (this *PushClient) SendScheduleBytes(content []byte, url string) (string, error) { 82 | ret, err := SendPostBytes2(url, content, this.AuthCode) 83 | if err != nil { 84 | return ret, err 85 | } 86 | if strings.Contains(ret, "schedule_id") { 87 | return ret, nil 88 | } else { 89 | return "", errors.New(ret) 90 | } 91 | 92 | } 93 | 94 | func (this *PushClient) SendGetReportRequest(msg_ids string, url string) (string, error) { 95 | return Get(url).SetBasicAuth(this.AppKey, this.MasterSecret).Param("msg_ids", msg_ids).String() 96 | } 97 | 98 | func UnmarshalResponse(rsp string) (map[string]interface{}, error) { 99 | mapRs := map[string]interface{}{} 100 | if len(strings.TrimSpace(rsp)) == 0 { 101 | return mapRs, nil 102 | } 103 | err := json.Unmarshal([]byte(rsp), &mapRs) 104 | if err != nil { 105 | return nil, err 106 | } 107 | if _, ok := mapRs["error"]; ok { 108 | return nil, fmt.Errorf(rsp) 109 | } 110 | return mapRs, nil 111 | } 112 | 113 | func (this *PushClient) SendDeleteScheduleRequest(schedule_id string, url string) (string, error) { 114 | rsp, err := Delete(strings.Join([]string{url, schedule_id}, "/")).Header("Authorization", this.AuthCode).String() 115 | if err != nil { 116 | return "", err 117 | } 118 | _, err = UnmarshalResponse(rsp) 119 | if err != nil { 120 | return "", err 121 | } 122 | return rsp, nil 123 | } 124 | func (this *PushClient) SendGetScheduleRequest(schedule_id string, url string) (string, error) { 125 | rsp, err := Get(strings.Join([]string{url, schedule_id}, "/")).Header("Authorization", this.AuthCode).String() 126 | if err != nil { 127 | return "", err 128 | } 129 | _, err = UnmarshalResponse(rsp) 130 | if err != nil { 131 | return "", err 132 | } 133 | return rsp, nil 134 | } 135 | -------------------------------------------------------------------------------- /httplib.go: -------------------------------------------------------------------------------- 1 | package jpushclient 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "encoding/xml" 8 | "io" 9 | "io/ioutil" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | // Get returns *HttpRequest with GET method. 19 | func Get(url string) *HttpRequest { 20 | var req http.Request 21 | req.Method = "GET" 22 | req.Header = http.Header{} 23 | return &HttpRequest{url, &req, map[string]string{}, 60 * time.Second, 60 * time.Second, nil, nil, nil} 24 | } 25 | 26 | // Post returns *HttpRequest with POST method. 27 | func Post(url string) *HttpRequest { 28 | var req http.Request 29 | req.Method = "POST" 30 | req.Header = http.Header{} 31 | return &HttpRequest{url, &req, map[string]string{}, 60 * time.Second, 60 * time.Second, nil, nil, nil} 32 | } 33 | 34 | func Delete(url string) *HttpRequest { 35 | var req http.Request 36 | req.Method = "DELETE" 37 | req.Header = http.Header{} 38 | return &HttpRequest{url, &req, map[string]string{}, 60 * time.Second, 60 * time.Second, nil, nil, nil} 39 | } 40 | 41 | // HttpRequest provides more useful methods for requesting one url than http.Request. 42 | type HttpRequest struct { 43 | url string 44 | req *http.Request 45 | params map[string]string 46 | connectTimeout time.Duration 47 | readWriteTimeout time.Duration 48 | tlsClientConfig *tls.Config 49 | proxy func(*http.Request) (*url.URL, error) 50 | transport http.RoundTripper 51 | } 52 | 53 | // SetTimeout sets connect time out and read-write time out for Request. 54 | func (b *HttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *HttpRequest { 55 | b.connectTimeout = connectTimeout 56 | b.readWriteTimeout = readWriteTimeout 57 | return b 58 | } 59 | 60 | func (b *HttpRequest) SetBasicAuth(userName, password string) *HttpRequest { 61 | b.req.SetBasicAuth(userName, password) 62 | return b 63 | } 64 | 65 | // SetTLSClientConfig sets tls connection configurations if visiting https url. 66 | func (b *HttpRequest) SetTLSClientConfig(config *tls.Config) *HttpRequest { 67 | b.tlsClientConfig = config 68 | return b 69 | } 70 | 71 | // Header add header item string in request. 72 | func (b *HttpRequest) Header(key, value string) *HttpRequest { 73 | b.req.Header.Set(key, value) 74 | return b 75 | } 76 | 77 | // Set the protocol version for incoming requests. 78 | // Client requests always use HTTP/1.1. 79 | func (b *HttpRequest) SetProtocolVersion(vers string) *HttpRequest { 80 | if len(vers) == 0 { 81 | vers = "HTTP/1.1" 82 | } 83 | 84 | major, minor, ok := http.ParseHTTPVersion(vers) 85 | if ok { 86 | b.req.Proto = vers 87 | b.req.ProtoMajor = major 88 | b.req.ProtoMinor = minor 89 | } 90 | 91 | return b 92 | } 93 | 94 | // SetCookie add cookie into request. 95 | func (b *HttpRequest) SetCookie(cookie *http.Cookie) *HttpRequest { 96 | b.req.Header.Add("Cookie", cookie.String()) 97 | return b 98 | } 99 | 100 | // Set transport to 101 | func (b *HttpRequest) SetTransport(transport http.RoundTripper) *HttpRequest { 102 | b.transport = transport 103 | return b 104 | } 105 | 106 | // Set http proxy 107 | // example: 108 | // 109 | // func(req *http.Request) (*url.URL, error) { 110 | // u, _ := url.ParseRequestURI("http://127.0.0.1:8118") 111 | // return u, nil 112 | // } 113 | func (b *HttpRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *HttpRequest { 114 | b.proxy = proxy 115 | return b 116 | } 117 | 118 | // Param adds query param in to request. 119 | // params build query string as ?key1=value1&key2=value2... 120 | func (b *HttpRequest) Param(key, value string) *HttpRequest { 121 | b.params[key] = value 122 | return b 123 | } 124 | 125 | // Body adds request raw body. 126 | // it supports string and []byte. 127 | func (b *HttpRequest) Body(data interface{}) *HttpRequest { 128 | switch t := data.(type) { 129 | case string: 130 | bf := bytes.NewBufferString(t) 131 | b.req.Body = ioutil.NopCloser(bf) 132 | b.req.ContentLength = int64(len(t)) 133 | case []byte: 134 | bf := bytes.NewBuffer(t) 135 | b.req.Body = ioutil.NopCloser(bf) 136 | b.req.ContentLength = int64(len(t)) 137 | } 138 | return b 139 | } 140 | 141 | func (b *HttpRequest) getResponse() (*http.Response, error) { 142 | var paramBody string 143 | if len(b.params) > 0 { 144 | var buf bytes.Buffer 145 | for k, v := range b.params { 146 | buf.WriteString(url.QueryEscape(k)) 147 | buf.WriteByte('=') 148 | buf.WriteString(url.QueryEscape(v)) 149 | buf.WriteByte('&') 150 | } 151 | paramBody = buf.String() 152 | paramBody = paramBody[0 : len(paramBody)-1] 153 | } 154 | 155 | if b.req.Method == "GET" && len(paramBody) > 0 { 156 | if strings.Index(b.url, "?") != -1 { 157 | b.url += "&" + paramBody 158 | } else { 159 | b.url = b.url + "?" + paramBody 160 | } 161 | } else if b.req.Method == "POST" && b.req.Body == nil && len(paramBody) > 0 { 162 | b.Header("Content-Type", "application/x-www-form-urlencoded") 163 | b.Body(paramBody) 164 | } 165 | 166 | url, err := url.Parse(b.url) 167 | if url.Scheme == "" { 168 | b.url = "http://" + b.url 169 | url, err = url.Parse(b.url) 170 | } 171 | if err != nil { 172 | return nil, err 173 | } 174 | 175 | b.req.URL = url 176 | trans := b.transport 177 | 178 | if trans == nil { 179 | // create default transport 180 | trans = &http.Transport{ 181 | TLSClientConfig: b.tlsClientConfig, 182 | Proxy: b.proxy, 183 | Dial: TimeoutDialer(b.connectTimeout, b.readWriteTimeout), 184 | } 185 | } else { 186 | // if b.transport is *http.Transport then set the settings. 187 | if t, ok := trans.(*http.Transport); ok { 188 | if t.TLSClientConfig == nil { 189 | t.TLSClientConfig = b.tlsClientConfig 190 | } 191 | if t.Proxy == nil { 192 | t.Proxy = b.proxy 193 | } 194 | if t.Dial == nil { 195 | t.Dial = TimeoutDialer(b.connectTimeout, b.readWriteTimeout) 196 | } 197 | } 198 | } 199 | 200 | client := &http.Client{ 201 | Transport: trans, 202 | } 203 | 204 | resp, err := client.Do(b.req) 205 | if err != nil { 206 | return nil, err 207 | } 208 | return resp, nil 209 | } 210 | 211 | // String returns the body string in response. 212 | // it calls Response inner. 213 | func (b *HttpRequest) String() (string, error) { 214 | data, err := b.Bytes() 215 | if err != nil { 216 | return "", err 217 | } 218 | 219 | return string(data), nil 220 | } 221 | 222 | // Bytes returns the body []byte in response. 223 | // it calls Response inner. 224 | func (b *HttpRequest) Bytes() ([]byte, error) { 225 | resp, err := b.getResponse() 226 | if err != nil { 227 | return nil, err 228 | } 229 | if resp.Body == nil { 230 | return nil, nil 231 | } 232 | defer resp.Body.Close() 233 | data, err := ioutil.ReadAll(resp.Body) 234 | if err != nil { 235 | return nil, err 236 | } 237 | return data, nil 238 | } 239 | 240 | // ToFile saves the body data in response to one file. 241 | // it calls Response inner. 242 | func (b *HttpRequest) ToFile(filename string) error { 243 | f, err := os.Create(filename) 244 | if err != nil { 245 | return err 246 | } 247 | defer f.Close() 248 | 249 | resp, err := b.getResponse() 250 | if err != nil { 251 | return err 252 | } 253 | if resp.Body == nil { 254 | return nil 255 | } 256 | defer resp.Body.Close() 257 | _, err = io.Copy(f, resp.Body) 258 | if err != nil { 259 | return err 260 | } 261 | return nil 262 | } 263 | 264 | // ToJson returns the map that marshals from the body bytes as json in response . 265 | // it calls Response inner. 266 | func (b *HttpRequest) ToJson(v interface{}) error { 267 | data, err := b.Bytes() 268 | if err != nil { 269 | return err 270 | } 271 | err = json.Unmarshal(data, v) 272 | if err != nil { 273 | return err 274 | } 275 | return nil 276 | } 277 | 278 | // ToXml returns the map that marshals from the body bytes as xml in response . 279 | // it calls Response inner. 280 | func (b *HttpRequest) ToXML(v interface{}) error { 281 | data, err := b.Bytes() 282 | if err != nil { 283 | return err 284 | } 285 | err = xml.Unmarshal(data, v) 286 | if err != nil { 287 | return err 288 | } 289 | return nil 290 | } 291 | 292 | // Response executes request client gets response mannually. 293 | func (b *HttpRequest) Response() (*http.Response, error) { 294 | return b.getResponse() 295 | } 296 | 297 | // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. 298 | func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { 299 | return func(netw, addr string) (net.Conn, error) { 300 | conn, err := net.DialTimeout(netw, addr, cTimeout) 301 | if err != nil { 302 | return nil, err 303 | } 304 | conn.SetDeadline(time.Now().Add(rwTimeout)) 305 | return conn, nil 306 | } 307 | } 308 | --------------------------------------------------------------------------------