<commit_before>// Package stockfighter provides a simple wrapper for the Stockfighter API:
//
// https://www.stockfighter.io/
//
// https://starfighter.readme.io/
package stockfighter

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"

	"github.com/gorilla/websocket"
)

func apiUrl(path string, args ...interface{}) string {
	return fmt.Sprintf("https://api.stockfighter.io/ob/api/"+path, args...)
}

func gmUrl(path string, args ...interface{}) string {
	return fmt.Sprintf("https://api.stockfighter.io/gm/"+path, args...)
}

func wsUrl(path string, args ...interface{}) string {
	return fmt.Sprintf("wss://api.stockfighter.io/ob/ws/"+path, args...)
}

type apiCall interface {
	Err() error
}

type response struct {
	Ok    bool
	Error string
}

func (r response) Err() error {
	if len(r.Error) > 0 {
		return fmt.Errorf(r.Error)
	}
	return nil
}

type venueResponse struct {
	response
	Venue string
}

type stocksResponse struct {
	response
	Symbols []Symbol
}

type orderBookResponse struct {
	response
	OrderBook
}

type quoteResponse struct {
	response
	Quote
}

type orderResponse struct {
	response
	OrderState
}

type bulkOrderResponse struct {
	response
	Venue  string
	Orders []OrderState
}

type quoteMessage struct {
	Ok bool
	Quote
}

type executionMessage struct {
	Ok bool
	Execution
}

type gameResponse struct {
	response
	Game
}

type gameStateResponse struct {
	response
	GameState
}

type Stockfighter struct {
	apiKey string
	debug  bool
}

// Create new Stockfighter API instance.
// If debug is true, log all HTTP requests and responses.
func NewStockfighter(apiKey string, debug bool) *Stockfighter {
	return &Stockfighter{
		apiKey: apiKey,
		debug:  debug,
	}
}

// Check the API Is Up. If venue is a non-empty string, then check that venue.
// Returns nil if ok, otherwise the error indicates the problem.
func (sf *Stockfighter) Heartbeat(venue string) error {
	var resp response
	url := apiUrl("heartbeat")
	if len(venue) > 0 {
		url = apiUrl("venues/%s/heartbeat", venue)
	}
	return sf.do("GET", url, nil, &resp)
}

// Get the stocks available for trading on a venue.
func (sf *Stockfighter) Stocks(venue string) ([]Symbol, error) {
	var resp stocksResponse
	url := apiUrl("venues/%s/stocks", venue)
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return resp.Symbols, nil
}

// Get the orderbook for a particular stock.
func (sf *Stockfighter) OrderBook(venue, stock string) (*OrderBook, error) {
	var resp orderBookResponse
	url := apiUrl("venues/%s/stocks/%s", venue, stock)
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.OrderBook, nil
}

// Get a quick look at the most recent trade information for a stock.
func (sf *Stockfighter) Quote(venue, stock string) (*Quote, error) {
	var resp quoteResponse
	url := apiUrl("venues/%s/stocks/%s/quote", venue, stock)
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.Quote, nil
}

// Place an order
func (sf *Stockfighter) Place(order *Order) (*OrderState, error) {
	body, err := encodeJson(order)
	if err != nil {
		return nil, err
	}
	var resp orderResponse
	url := apiUrl("venues/%s/stocks/%s/orders", order.Venue, order.Stock)
	if err := sf.do("POST", url, body, &resp); err != nil {
		return nil, err
	}
	return &resp.OrderState, nil
}

// Get the status for an existing order.
func (sf *Stockfighter) Status(venue, stock string, id uint64) (*OrderState, error) {
	var resp orderResponse
	url := apiUrl("venues/%s/stocks/%s/orders/%d", venue, stock, id)
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.OrderState, nil
}

// Get the statuses for all an account's orders of a stock on a venue. If stock is a non-empty string, only statuses for that stock are returned
func (sf *Stockfighter) StockStatus(account, venue, stock string) ([]OrderState, error) {
	url := apiUrl("venues/%s/accounts/%s/orders", venue, account)
	if len(stock) > 0 {
		url = apiUrl("venues/%s/accounts/%s/stocks/%s/orders", venue, account, stock)
	}
	var resp bulkOrderResponse
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return resp.Orders, nil
}

// Cancel an existing order
func (sf *Stockfighter) Cancel(venue, stock string, id uint64) error {
	var resp response
	url := apiUrl("venues/%s/stocks/%s/orders/%d", venue, stock, id)
	return sf.do("DELETE", url, nil, &resp)
}

// Subscribe to a stream of quotes for a venue. If stock is a non-empy string, only quotes for that stock are returned.
func (sf *Stockfighter) Quotes(account, venue, stock string) (chan *Quote, error) {
	url := wsUrl("%s/venues/%s/tickertape", account, venue)
	if len(stock) > 0 {
		url = wsUrl("%s/venues/%s/stocks/%s/tickertape", account, venue, stock)
	}
	c := make(chan *Quote)
	return c, sf.pump(url, func(conn *websocket.Conn) error {
		var quote quoteMessage
		if err := conn.ReadJSON(&quote); err != nil {
			close(c)
			return err
		}
		if quote.Ok {
			c <- &quote.Quote
		}
		return nil
	})
}

// Subscribe to a stream of executions for a venue. If stock is a non-empy string, only executions for that stock are returned.
func (sf *Stockfighter) Executions(account, venue, stock string) (chan *Execution, error) {
	url := wsUrl("%s/venues/%s/executions", account, venue)
	if len(stock) > 0 {
		url = wsUrl("%s/venues/%s/stocks/%s/executions", account, venue, stock)
	}
	c := make(chan *Execution)
	return c, sf.pump(url, func(conn *websocket.Conn) error {
		var execution executionMessage
		if err := conn.ReadJSON(&execution); err != nil {
			close(c)
			return err
		}
		if execution.Ok {
			c <- &execution.Execution
		}
		return nil
	})
}

// Start a new level.
func (sf *Stockfighter) Start(level string) (*Game, error) {
	var resp gameResponse
	url := gmUrl("levels/%s", level)
	if err := sf.do("POST", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.Game, nil
}

// Restart a level using the instance id from a previously started Game.
func (sf *Stockfighter) Restart(id uint64) error {
	var resp response
	url := gmUrl("instances/%d/restart", id)
	return sf.do("POST", url, nil, &resp)
}

// Resume a level using the instance id from a previously started Game.
func (sf *Stockfighter) Resume(id uint64) error {
	var resp response
	url := gmUrl("instances/%d/resume", id)
	return sf.do("POST", url, nil, &resp)
}

// Stop a level using the instance id from a previously started Game.
func (sf *Stockfighter) Stop(id uint64) error {
	var resp response
	url := gmUrl("instances/%d/stop", id)
	return sf.do("POST", url, nil, &resp)
}

// Get the GameState using the instance id from a previously started Game.
func (sf *Stockfighter) GameStatus(id uint64) (*GameState, error) {
	var resp gameStateResponse
	url := gmUrl("instances/%d", id)
	if err := sf.do("Get", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.GameState, nil
}

func encodeJson(v interface{}) (io.Reader, error) {
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(v); err != nil {
		return nil, err
	}
	return &buf, nil
}

func (sf *Stockfighter) do(method, url string, body io.Reader, value apiCall) error {
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return err
	}
	req.Header.Add("X-Starfighter-Authorization", sf.apiKey)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if sf.debug {
		out, _ := httputil.DumpRequest(req, true)
		log.Println(string(out))
		out, _ = httputil.DumpResponse(resp, true)
		log.Println(string(out))
	}
	if err := json.NewDecoder(resp.Body).Decode(value); err != nil {
		return err
	}
	if err := value.Err(); err != nil {
		return err
	}
	return nil
}

func (sf *Stockfighter) pump(url string, f func(*websocket.Conn) error) error {
	conn, _, err := websocket.DefaultDialer.Dial(url, nil)
	if err != nil {
		return err
	}
	go func() {
		defer conn.Close()
		for err := f(conn); err == nil; err = f(conn) {
		}
	}()
	return nil
}
<commit_msg>Shorten comment lines<commit_after>// Package stockfighter provides a simple wrapper for the Stockfighter API:
//
// https://www.stockfighter.io/
//
// https://starfighter.readme.io/
package stockfighter

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"

	"github.com/gorilla/websocket"
)

func apiUrl(path string, args ...interface{}) string {
	return fmt.Sprintf("https://api.stockfighter.io/ob/api/"+path, args...)
}

func gmUrl(path string, args ...interface{}) string {
	return fmt.Sprintf("https://api.stockfighter.io/gm/"+path, args...)
}

func wsUrl(path string, args ...interface{}) string {
	return fmt.Sprintf("wss://api.stockfighter.io/ob/ws/"+path, args...)
}

type apiCall interface {
	Err() error
}

type response struct {
	Ok    bool
	Error string
}

func (r response) Err() error {
	if len(r.Error) > 0 {
		return fmt.Errorf(r.Error)
	}
	return nil
}

type venueResponse struct {
	response
	Venue string
}

type stocksResponse struct {
	response
	Symbols []Symbol
}

type orderBookResponse struct {
	response
	OrderBook
}

type quoteResponse struct {
	response
	Quote
}

type orderResponse struct {
	response
	OrderState
}

type bulkOrderResponse struct {
	response
	Venue  string
	Orders []OrderState
}

type quoteMessage struct {
	Ok bool
	Quote
}

type executionMessage struct {
	Ok bool
	Execution
}

type gameResponse struct {
	response
	Game
}

type gameStateResponse struct {
	response
	GameState
}

type Stockfighter struct {
	apiKey string
	debug  bool
}

// Create new Stockfighter API instance.
// If debug is true, log all HTTP requests and responses.
func NewStockfighter(apiKey string, debug bool) *Stockfighter {
	return &Stockfighter{
		apiKey: apiKey,
		debug:  debug,
	}
}

// Check the API Is Up. If venue is a non-empty string, then check that venue.
// Returns nil if ok, otherwise the error indicates the problem.
func (sf *Stockfighter) Heartbeat(venue string) error {
	var resp response
	url := apiUrl("heartbeat")
	if len(venue) > 0 {
		url = apiUrl("venues/%s/heartbeat", venue)
	}
	return sf.do("GET", url, nil, &resp)
}

// Get the stocks available for trading on a venue.
func (sf *Stockfighter) Stocks(venue string) ([]Symbol, error) {
	var resp stocksResponse
	url := apiUrl("venues/%s/stocks", venue)
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return resp.Symbols, nil
}

// Get the orderbook for a particular stock.
func (sf *Stockfighter) OrderBook(venue, stock string) (*OrderBook, error) {
	var resp orderBookResponse
	url := apiUrl("venues/%s/stocks/%s", venue, stock)
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.OrderBook, nil
}

// Get a quick look at the most recent trade information for a stock.
func (sf *Stockfighter) Quote(venue, stock string) (*Quote, error) {
	var resp quoteResponse
	url := apiUrl("venues/%s/stocks/%s/quote", venue, stock)
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.Quote, nil
}

// Place an order
func (sf *Stockfighter) Place(order *Order) (*OrderState, error) {
	body, err := encodeJson(order)
	if err != nil {
		return nil, err
	}
	var resp orderResponse
	url := apiUrl("venues/%s/stocks/%s/orders", order.Venue, order.Stock)
	if err := sf.do("POST", url, body, &resp); err != nil {
		return nil, err
	}
	return &resp.OrderState, nil
}

// Get the status for an existing order.
func (sf *Stockfighter) Status(venue, stock string, id uint64) (*OrderState, error) {
	var resp orderResponse
	url := apiUrl("venues/%s/stocks/%s/orders/%d", venue, stock, id)
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.OrderState, nil
}

// Get the statuses for all an account's orders of a stock on a venue.
// If stock is a non-empty string, only statuses for that stock are returned
func (sf *Stockfighter) StockStatus(account, venue, stock string) ([]OrderState, error) {
	url := apiUrl("venues/%s/accounts/%s/orders", venue, account)
	if len(stock) > 0 {
		url = apiUrl("venues/%s/accounts/%s/stocks/%s/orders", venue, account, stock)
	}
	var resp bulkOrderResponse
	if err := sf.do("GET", url, nil, &resp); err != nil {
		return nil, err
	}
	return resp.Orders, nil
}

// Cancel an existing order
func (sf *Stockfighter) Cancel(venue, stock string, id uint64) error {
	var resp response
	url := apiUrl("venues/%s/stocks/%s/orders/%d", venue, stock, id)
	return sf.do("DELETE", url, nil, &resp)
}

// Subscribe to a stream of quotes for a venue.
// If stock is a non-empy string, only quotes for that stock are returned.
func (sf *Stockfighter) Quotes(account, venue, stock string) (chan *Quote, error) {
	url := wsUrl("%s/venues/%s/tickertape", account, venue)
	if len(stock) > 0 {
		url = wsUrl("%s/venues/%s/stocks/%s/tickertape", account, venue, stock)
	}
	c := make(chan *Quote)
	return c, sf.pump(url, func(conn *websocket.Conn) error {
		var quote quoteMessage
		if err := conn.ReadJSON(&quote); err != nil {
			close(c)
			return err
		}
		if quote.Ok {
			c <- &quote.Quote
		}
		return nil
	})
}

// Subscribe to a stream of executions for a venue.
// If stock is a non-empy string, only executions for that stock are returned.
func (sf *Stockfighter) Executions(account, venue, stock string) (chan *Execution, error) {
	url := wsUrl("%s/venues/%s/executions", account, venue)
	if len(stock) > 0 {
		url = wsUrl("%s/venues/%s/stocks/%s/executions", account, venue, stock)
	}
	c := make(chan *Execution)
	return c, sf.pump(url, func(conn *websocket.Conn) error {
		var execution executionMessage
		if err := conn.ReadJSON(&execution); err != nil {
			close(c)
			return err
		}
		if execution.Ok {
			c <- &execution.Execution
		}
		return nil
	})
}

// Start a new level.
func (sf *Stockfighter) Start(level string) (*Game, error) {
	var resp gameResponse
	url := gmUrl("levels/%s", level)
	if err := sf.do("POST", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.Game, nil
}

// Restart a level using the instance id from a previously started Game.
func (sf *Stockfighter) Restart(id uint64) error {
	var resp response
	url := gmUrl("instances/%d/restart", id)
	return sf.do("POST", url, nil, &resp)
}

// Resume a level using the instance id from a previously started Game.
func (sf *Stockfighter) Resume(id uint64) error {
	var resp response
	url := gmUrl("instances/%d/resume", id)
	return sf.do("POST", url, nil, &resp)
}

// Stop a level using the instance id from a previously started Game.
func (sf *Stockfighter) Stop(id uint64) error {
	var resp response
	url := gmUrl("instances/%d/stop", id)
	return sf.do("POST", url, nil, &resp)
}

// Get the GameState using the instance id from a previously started Game.
func (sf *Stockfighter) GameStatus(id uint64) (*GameState, error) {
	var resp gameStateResponse
	url := gmUrl("instances/%d", id)
	if err := sf.do("Get", url, nil, &resp); err != nil {
		return nil, err
	}
	return &resp.GameState, nil
}

func encodeJson(v interface{}) (io.Reader, error) {
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(v); err != nil {
		return nil, err
	}
	return &buf, nil
}

func (sf *Stockfighter) do(method, url string, body io.Reader, value apiCall) error {
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return err
	}
	req.Header.Add("X-Starfighter-Authorization", sf.apiKey)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if sf.debug {
		out, _ := httputil.DumpRequest(req, true)
		log.Println(string(out))
		out, _ = httputil.DumpResponse(resp, true)
		log.Println(string(out))
	}
	if err := json.NewDecoder(resp.Body).Decode(value); err != nil {
		return err
	}
	if err := value.Err(); err != nil {
		return err
	}
	return nil
}

func (sf *Stockfighter) pump(url string, f func(*websocket.Conn) error) error {
	conn, _, err := websocket.DefaultDialer.Dial(url, nil)
	if err != nil {
		return err
	}
	go func() {
		defer conn.Close()
		for err := f(conn); err == nil; err = f(conn) {
		}
	}()
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	log "github.com/Sirupsen/logrus"

	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

type UsersResource struct {
	Data []User `json:"data"`
}

type UserResource struct {
	Data User `json:"data"`
}

type BookingResource struct {
	Data Booking `json:"data"`
}

// Structure representing error
type errorResponse struct {
	Msg string `json:"msg"`
}

var ServerName = "http://localhost:8080"

func responseDetailsFromMongoError(error interface{}) (content errorResponse, code int) {
	content = errorResponse{Msg: fmt.Sprint(error)}
	code = 400
	if content.Msg == "not found" {
		code = 404
	}
	return content, code
}

// write json response to http response
func writeJsonResponse(w http.ResponseWriter, content *[]byte, code int) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	w.Write(*content)
}

// addUserHandler used to add new user
func (h *HTTPClientHandler) addUserHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", ServerName)
	// adding new user to database
	var userRequest User

	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)

	if err != nil {
		// failed to read response body
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Could not read response body!")
		http.Error(w, "Failed to read request body.", 400)
		return
	}

	err = json.Unmarshal(body, &userRequest)

	if err != nil {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(422) // can't process this entity
		return
	}

	log.WithFields(log.Fields{
		"firstName":     userRequest.FirstName,
		"lastName":      userRequest.LastName,
		"userID":        userRequest.UserID,
		"profilePicUrl": userRequest.ProfilePicUrl,
		"gender":        userRequest.Gender,
		"body":          string(body),
	}).Info("Got user info")

	// adding user
	err = h.db.addUser(userRequest)

	if err == nil {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(201) // user inserted
		return
	} else {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Warn("Failed to insert..")

		content, code := responseDetailsFromMongoError(err)

		// Marshal provided interface into JSON structure
		uj, _ := json.Marshal(content)

		// Write content-type, statuscode, payload
		writeJsonResponse(w, &uj, code)

	}

}

// getAllUsersHandler used to get all users
func (h *HTTPClientHandler) getAllUsersHandler(w http.ResponseWriter, r *http.Request) {

	userid, _ := r.URL.Query()["q"]
	// looking for specific user
	if len(userid) > 0 {
		log.WithFields(log.Fields{
			"userid": userid[0],
		}).Info("Looking for user..")

		user, err := h.db.getUser(userid[0])

		if err == nil {
			// Marshal provided interface into JSON structure
			response := UserResource{Data: user}
			uj, _ := json.Marshal(response)

			// Write content-type, statuscode, payload
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(200)
			fmt.Fprintf(w, "%s", uj)
			return
		} else {
			log.WithFields(log.Fields{
				"error": err.Error(),
			}).Warn("Failed to insert..")

			content, code := responseDetailsFromMongoError(err)

			// Marshal provided interface into JSON structure
			uj, _ := json.Marshal(content)

			// Write content-type, statuscode, payload
			writeJsonResponse(w, &uj, code)
			return

		}
	}

	log.Warn(len(userid))
	// displaying all users
	results, err := h.db.getUsers()
	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Got error when tried to get all users")
	}

	log.WithFields(log.Fields{
		"count": len(results),
	}).Info("number of users")

	// Marshal provided interface into JSON structure
	response := UsersResource{Data: results}
	uj, _ := json.Marshal(response)

	// Write content-type, statuscode, payload
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	fmt.Fprintf(w, "%s", uj)
}

// addPlaceHandler add new hosting place, provide json
func (h *HTTPClientHandler) addPlaceHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", ServerName)
	// adding new hosting place to database
	var hostingPlaceRequest HostingPlace
	log.Info("adding place........")
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)

	if err != nil {
		// failed to read response body
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Could not read response body!")
		http.Error(w, "Failed to read request body.", 400)
		return
	}

	err = json.Unmarshal(body, &hostingPlaceRequest)

	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Failed to unmarshal json!")
	}

	log.WithFields(log.Fields{
		"body":   string(body),
		"host":   hostingPlaceRequest.Host,
		"active": hostingPlaceRequest.Active,
		"lat":    hostingPlaceRequest.Lat,
		"spaces": hostingPlaceRequest.Spaces,
		"long":   hostingPlaceRequest.Long,
	}).Info("Got place info")

	err = h.db.addHostingPlace(hostingPlaceRequest)

	if err == nil {
		// adding it to esri
		h.addEsriNode(hostingPlaceRequest)

		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(201) // place inserted
		return
	} else {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Warn("Failed to insert hosting place..")

		content, code := responseDetailsFromMongoError(err)

		// Marshal provided interface into JSON structure
		uj, _ := json.Marshal(content)

		// Write content-type, statuscode, payload
		writeJsonResponse(w, &uj, code)

	}

}

func (h *HTTPClientHandler) getPlaceHandler(w http.ResponseWriter, r *http.Request) {

}

func (h *HTTPClientHandler) addBookingHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", ServerName)
	// adding new hosting place to database
	var bookingRequest Booking
	log.Info("adding place........")
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)

	if err != nil {
		// failed to read response body
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Could not read response body!")
		http.Error(w, "Failed to read request body.", 400)
		return
	}

	err = json.Unmarshal(body, &bookingRequest)

	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Failed to unmarshal json!")
	}

	log.WithFields(log.Fields{
		"body": string(body),
		"host": bookingRequest.Host,
		"user": bookingRequest.User,
		"lat":  bookingRequest.Lat,
		"long": bookingRequest.Long,
	}).Info("Got place info")

	err = h.db.addBooking(bookingRequest)

	if err == nil {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(201) // booking inserted
		return
	} else {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Warn("Failed to insert booking, fork it..")

		content, code := responseDetailsFromMongoError(err)

		// Marshal provided interface into JSON structure
		uj, _ := json.Marshal(content)

		// Write content-type, statuscode, payload
		writeJsonResponse(w, &uj, code)

	}
}

func (h *HTTPClientHandler) getCurrentBooking(w http.ResponseWriter, r *http.Request) {
	userid, _ := r.URL.Query()["user"]

	if len(userid) > 0 {
		booking, err := h.db.getBooking(userid[0])
		if err != nil {
			log.Error(err.Error())
		}
		// Marshal provided interface into JSON structure
		response := BookingResource{Data: booking}
		uj, _ := json.Marshal(response)

		// Write content-type, statuscode, payload
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(200)
		fmt.Fprintf(w, "%s", uj)
	} else {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(412)
	}

}
<commit_msg>booking doesnt not require coords<commit_after>package main

import (
	log "github.com/Sirupsen/logrus"

	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

type UsersResource struct {
	Data []User `json:"data"`
}

type UserResource struct {
	Data User `json:"data"`
}

type BookingResource struct {
	Data Booking `json:"data"`
}

// Structure representing error
type errorResponse struct {
	Msg string `json:"msg"`
}

var ServerName = "http://localhost:8080"

func responseDetailsFromMongoError(error interface{}) (content errorResponse, code int) {
	content = errorResponse{Msg: fmt.Sprint(error)}
	code = 400
	if content.Msg == "not found" {
		code = 404
	}
	return content, code
}

// write json response to http response
func writeJsonResponse(w http.ResponseWriter, content *[]byte, code int) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	w.Write(*content)
}

// addUserHandler used to add new user
func (h *HTTPClientHandler) addUserHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", ServerName)
	// adding new user to database
	var userRequest User

	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)

	if err != nil {
		// failed to read response body
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Could not read response body!")
		http.Error(w, "Failed to read request body.", 400)
		return
	}

	err = json.Unmarshal(body, &userRequest)

	if err != nil {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(422) // can't process this entity
		return
	}

	log.WithFields(log.Fields{
		"firstName":     userRequest.FirstName,
		"lastName":      userRequest.LastName,
		"userID":        userRequest.UserID,
		"profilePicUrl": userRequest.ProfilePicUrl,
		"gender":        userRequest.Gender,
		"body":          string(body),
	}).Info("Got user info")

	// adding user
	err = h.db.addUser(userRequest)

	if err == nil {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(201) // user inserted
		return
	} else {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Warn("Failed to insert..")

		content, code := responseDetailsFromMongoError(err)

		// Marshal provided interface into JSON structure
		uj, _ := json.Marshal(content)

		// Write content-type, statuscode, payload
		writeJsonResponse(w, &uj, code)

	}

}

// getAllUsersHandler used to get all users
func (h *HTTPClientHandler) getAllUsersHandler(w http.ResponseWriter, r *http.Request) {

	userid, _ := r.URL.Query()["q"]
	// looking for specific user
	if len(userid) > 0 {
		log.WithFields(log.Fields{
			"userid": userid[0],
		}).Info("Looking for user..")

		user, err := h.db.getUser(userid[0])

		if err == nil {
			// Marshal provided interface into JSON structure
			response := UserResource{Data: user}
			uj, _ := json.Marshal(response)

			// Write content-type, statuscode, payload
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(200)
			fmt.Fprintf(w, "%s", uj)
			return
		} else {
			log.WithFields(log.Fields{
				"error": err.Error(),
			}).Warn("Failed to insert..")

			content, code := responseDetailsFromMongoError(err)

			// Marshal provided interface into JSON structure
			uj, _ := json.Marshal(content)

			// Write content-type, statuscode, payload
			writeJsonResponse(w, &uj, code)
			return

		}
	}

	log.Warn(len(userid))
	// displaying all users
	results, err := h.db.getUsers()
	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Got error when tried to get all users")
	}

	log.WithFields(log.Fields{
		"count": len(results),
	}).Info("number of users")

	// Marshal provided interface into JSON structure
	response := UsersResource{Data: results}
	uj, _ := json.Marshal(response)

	// Write content-type, statuscode, payload
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	fmt.Fprintf(w, "%s", uj)
}

// addPlaceHandler add new hosting place, provide json
func (h *HTTPClientHandler) addPlaceHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", ServerName)
	// adding new hosting place to database
	var hostingPlaceRequest HostingPlace
	log.Info("adding place........")
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)

	if err != nil {
		// failed to read response body
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Could not read response body!")
		http.Error(w, "Failed to read request body.", 400)
		return
	}

	err = json.Unmarshal(body, &hostingPlaceRequest)

	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Failed to unmarshal json!")
	}

	log.WithFields(log.Fields{
		"body":   string(body),
		"host":   hostingPlaceRequest.Host,
		"active": hostingPlaceRequest.Active,
		"lat":    hostingPlaceRequest.Lat,
		"spaces": hostingPlaceRequest.Spaces,
		"long":   hostingPlaceRequest.Long,
	}).Info("Got place info")

	err = h.db.addHostingPlace(hostingPlaceRequest)

	if err == nil {
		// adding it to esri
		h.addEsriNode(hostingPlaceRequest)

		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(201) // place inserted
		return
	} else {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Warn("Failed to insert hosting place..")

		content, code := responseDetailsFromMongoError(err)

		// Marshal provided interface into JSON structure
		uj, _ := json.Marshal(content)

		// Write content-type, statuscode, payload
		writeJsonResponse(w, &uj, code)

	}

}

func (h *HTTPClientHandler) getPlaceHandler(w http.ResponseWriter, r *http.Request) {

}

func (h *HTTPClientHandler) addBookingHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", ServerName)
	// adding new hosting place to database
	var bookingRequest Booking
	log.Info("adding place........")
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)

	if err != nil {
		// failed to read response body
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Could not read response body!")
		http.Error(w, "Failed to read request body.", 400)
		return
	}

	err = json.Unmarshal(body, &bookingRequest)

	if err != nil {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Error("Failed to unmarshal json!")
	}

	log.WithFields(log.Fields{
		"body": string(body),
		"host": bookingRequest.Host,
		"user": bookingRequest.User,
	}).Info("Got place info")

	err = h.db.addBooking(bookingRequest)

	if err == nil {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(201) // booking inserted
		return
	} else {
		log.WithFields(log.Fields{
			"error": err.Error(),
		}).Warn("Failed to insert booking, fork it..")

		content, code := responseDetailsFromMongoError(err)

		// Marshal provided interface into JSON structure
		uj, _ := json.Marshal(content)

		// Write content-type, statuscode, payload
		writeJsonResponse(w, &uj, code)

	}
}

func (h *HTTPClientHandler) getCurrentBooking(w http.ResponseWriter, r *http.Request) {
	userid, _ := r.URL.Query()["user"]

	if len(userid) > 0 {
		booking, err := h.db.getBooking(userid[0])
		if err != nil {
			log.Error(err.Error())
		}
		// Marshal provided interface into JSON structure
		response := BookingResource{Data: booking}
		uj, _ := json.Marshal(response)

		// Write content-type, statuscode, payload
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(200)
		fmt.Fprintf(w, "%s", uj)
	} else {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(412)
	}

}
<|endoftext|>


<commit_before>package rproxy

import (
	"fmt"
	"github.com/unrolled/render"
	"github.com/vanng822/accesslog"
	"github.com/vanng822/r2router"
	"github.com/vanng822/recovery"
	"net/http"
)

func (p *Proxy) AdminAPI() *r2router.Seefor {

	logger := accesslog.New()
	rec := recovery.NewRecovery()

	renderer := render.New()

	seefor := r2router.NewSeeforRouter()
	seefor.Before(rec.Handler)
	seefor.Before(logger.Handler)

	seefor.Group("/_server", func(r *r2router.GroupRouter) {
		r.Post("/backend", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			err, severConfig := p.ParseServerConfig(req)
			if err != nil {
				http.Error(w, fmt.Sprintf("Invalid server config, error: %s", err.Error()), http.StatusBadRequest)
				return
			}
			err = p.Register(severConfig.ServerName, severConfig.TargetUrl)
			if err != nil {
				http.Error(w,
					fmt.Sprintf("It was problem when adding new server, serverName: '%s', targetUrl: '%s', error: '%s'",
						severConfig.ServerName, severConfig.TargetUrl, err.Error()),
					http.StatusInternalServerError)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Write([]byte("OK"))

		})
		// delete backend node
		r.Delete("/backend", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			err, severConfig := p.ParseServerConfig(req)
			if err != nil {
				http.Error(w, fmt.Sprintf("Invalid server config, error: %s", err.Error()), http.StatusBadRequest)
				return
			}
			err = p.Unregister(severConfig.ServerName, severConfig.TargetUrl)
			if err != nil {
				http.Error(w,
					fmt.Sprintf("It was problem when removing server, serverName: '%s', targetUrl: '%s', error: '%s'",
						severConfig.ServerName, severConfig.TargetUrl, err.Error()),
					http.StatusInternalServerError)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Write([]byte("OK"))

		})
		// delete server
		r.Delete("/", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			req.ParseForm()
			serverName := req.Form.Get("serverName")
			if serverName == "" {
				http.Error(w, fmt.Sprintf("serverName is required"), http.StatusBadRequest)
				return
			}
			err := p.RemoveServer(serverName)
			if err != nil {
				http.Error(w,
					fmt.Sprintf("It was problem when removing server, serverName: '%s', error: '%s'",
						serverName, err.Error()),
					http.StatusInternalServerError)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Write([]byte("OK"))
		})
		// list all node for a servername
		r.Get("/backend", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			req.ParseForm()
			serverName := req.Form.Get("serverName")
			if serverName == "" {
				http.Error(w, fmt.Sprintf("serverName is required"), http.StatusBadRequest)
				return
			}
			server, ok := p.servers[serverName]
			if !ok {
				http.Error(w,
					fmt.Sprintf("Could not find server by name: %s", serverName),
					http.StatusBadRequest)
				return
			}
			data := r2router.M{}
			nodes := make([]string, len(server.backend.nodes))
			for i, node := range server.backend.nodes {
				nodes[i] = node.targetUrl
			}
			data[serverName] = nodes
			renderer.JSON(w, http.StatusOK, data)
		})
		// list all servernames
		r.Get("/", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			data := r2router.M{}
			for serverName, server := range p.servers {
				nodes := make([]string, len(server.backend.nodes))
				for i, node := range server.backend.nodes {
					nodes[i] = node.targetUrl
				}
				data[serverName] = nodes
			}
			renderer.JSON(w, http.StatusOK, data)
		})
	})

	return seefor
}
<commit_msg>Make response more consistent when 200<commit_after>package rproxy

import (
	"fmt"
	"github.com/unrolled/render"
	"github.com/vanng822/accesslog"
	"github.com/vanng822/r2router"
	"github.com/vanng822/recovery"
	"net/http"
)

func (p *Proxy) AdminAPI() *r2router.Seefor {

	logger := accesslog.New()
	rec := recovery.NewRecovery()

	renderer := render.New()

	seefor := r2router.NewSeeforRouter()
	seefor.Before(rec.Handler)
	seefor.Before(logger.Handler)

	seefor.Group("/_server", func(r *r2router.GroupRouter) {
		r.Post("/backend", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			err, severConfig := p.ParseServerConfig(req)
			if err != nil {
				http.Error(w, fmt.Sprintf("Invalid server config, error: %s", err.Error()), http.StatusBadRequest)
				return
			}
			err = p.Register(severConfig.ServerName, severConfig.TargetUrl)
			if err != nil {
				http.Error(w,
					fmt.Sprintf("It was problem when adding new server, serverName: '%s', targetUrl: '%s', error: '%s'",
						severConfig.ServerName, severConfig.TargetUrl, err.Error()),
					http.StatusInternalServerError)
				return
			}
			renderer.JSON(w, http.StatusOK, r2router.M{"status": "OK"})
		})
		// delete backend node
		r.Delete("/backend", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			err, severConfig := p.ParseServerConfig(req)
			if err != nil {
				http.Error(w, fmt.Sprintf("Invalid server config, error: %s", err.Error()), http.StatusBadRequest)
				return
			}
			err = p.Unregister(severConfig.ServerName, severConfig.TargetUrl)
			if err != nil {
				http.Error(w,
					fmt.Sprintf("It was problem when removing server, serverName: '%s', targetUrl: '%s', error: '%s'",
						severConfig.ServerName, severConfig.TargetUrl, err.Error()),
					http.StatusInternalServerError)
				return
			}
			renderer.JSON(w, http.StatusOK, r2router.M{"status": "OK"})

		})
		// delete server
		r.Delete("/", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			req.ParseForm()
			serverName := req.Form.Get("serverName")
			if serverName == "" {
				http.Error(w, fmt.Sprintf("serverName is required"), http.StatusBadRequest)
				return
			}
			err := p.RemoveServer(serverName)
			if err != nil {
				http.Error(w,
					fmt.Sprintf("It was problem when removing server, serverName: '%s', error: '%s'",
						serverName, err.Error()),
					http.StatusInternalServerError)
				return
			}
			renderer.JSON(w, http.StatusOK, r2router.M{"status": "OK"})
		})
		// list all node for a servername
		r.Get("/backend", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			req.ParseForm()
			serverName := req.Form.Get("serverName")
			if serverName == "" {
				http.Error(w, fmt.Sprintf("serverName is required"), http.StatusBadRequest)
				return
			}
			server, ok := p.servers[serverName]
			if !ok {
				http.Error(w,
					fmt.Sprintf("Could not find server by name: %s", serverName),
					http.StatusBadRequest)
				return
			}
			data := r2router.M{}
			nodes := make([]string, len(server.backend.nodes))
			for i, node := range server.backend.nodes {
				nodes[i] = node.targetUrl
			}
			data[serverName] = nodes
			renderer.JSON(w, http.StatusOK, r2router.M{"status": "OK", "result": data})
		})
		// list all servernames
		r.Get("/", func(w http.ResponseWriter, req *http.Request, _ r2router.Params) {
			data := r2router.M{}
			for serverName, server := range p.servers {
				nodes := make([]string, len(server.backend.nodes))
				for i, node := range server.backend.nodes {
					nodes[i] = node.targetUrl
				}
				data[serverName] = nodes
			}
			renderer.JSON(w, http.StatusOK, r2router.M{"status": "OK", "result": data})
		})
	})

	return seefor
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"crypto/rsa"
	"encoding/base64"
	"github.com/realglobe-Inc/edo/util"
	"github.com/realglobe-Inc/go-lib-rg/erro"
	"io"
	"net"
	"net/http"
	"net/url"
	"time"
)

const (
	headerTaId       = "X-Edo-Ta-Id"
	headerTaToken    = "X-Edo-Ta-Token"
	headerTaTokenSig = "X-Edo-Ta-Token-Sign"
	headerHashFunc   = "X-Edo-Hash-Function"

	headerTaAuthErr = "X-Edo-Ta-Auth-Error"

	cookieTaSess = "X-Edo-Ta-Session"
)

func uriBase(url *url.URL) string {
	return url.Scheme + "://" + url.Host + url.Path
}

// Web プロキシ。
func proxyApi(sys *system, w http.ResponseWriter, r *http.Request) error {

	taId := r.Header.Get(headerTaId)
	if taId == "" {
		taId = sys.taId
	}

	sess, _, err := sys.session(uriBase(r.URL), taId, nil)
	if err != nil {
		return erro.Wrap(err)
	}

	if sess != nil {
		// セッション確立済み。
		log.Debug("authenticated session is exist")
		return forward(sys, w, r, taId, sess)
	} else {
		// セッション未確立。
		log.Debug("session is not exist")
		return startSession(sys, w, r, taId)
	}
}

// 転送する。
func forward(sys *system, w http.ResponseWriter, r *http.Request, taId string, sess *session) error {
	r.AddCookie(&http.Cookie{Name: cookieTaSess, Value: sess.id})
	r.RequestURI = ""

	////////////////////////////////////////////////////////////
	util.LogRequest(r, true)
	////////////////////////////////////////////////////////////
	resp, err := sess.cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()
	////////////////////////////////////////////////////////////
	util.LogResponse(resp, true)
	////////////////////////////////////////////////////////////

	log.Debug("forwarded")

	if resp.StatusCode == http.StatusUnauthorized && resp.Header.Get(headerTaAuthErr) != "" {
		// edo-auth で 401 Unauthorized なら、タイミングの問題なので startSession からやり直す。
		// 古いセッションは上書きされるので消す必要無し。
		return startSession(sys, w, r, taId)
	}

	return copyResponse(resp, w)
}

// セッション開始。
func startSession(sys *system, w http.ResponseWriter, r *http.Request, taId string) error {

	cli := &http.Client{}

	r.RequestURI = ""

	////////////////////////////////////////////////////////////
	util.LogRequest(r, true)
	////////////////////////////////////////////////////////////
	resp, err := cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()
	////////////////////////////////////////////////////////////
	util.LogResponse(resp, true)
	////////////////////////////////////////////////////////////

	log.Debug("sent raw request")

	if resp.Header.Get(headerTaAuthErr) == "" || resp.StatusCode != http.StatusUnauthorized {
		// 相手側が TA 認証を必要としていなかったのかもしれない。
		return copyResponse(resp, w)
	}

	// 相手側 TA も認証始めた。
	log.Debug("authentication started")

	sess, sessToken := parseSession(resp)
	if sess == nil {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no cookie "+cookieTaSess, nil))
	} else if sessToken == "" {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no header field "+headerTaToken, nil))
	}

	expiDate := getExpirationDate(sess)

	// 認証用データが揃ってた。
	log.Debug("authentication data was found")

	priKey, _, err := sys.privateKey(taId, nil)
	if err != nil {
		return erro.Wrap(err)
	} else if priKey == nil {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no private key of "+taId, nil))
	}

	// 秘密鍵を用意できた。
	log.Debug("private key of " + taId + " is exist")

	hashName := r.Header.Get(headerHashFunc)
	if hashName == "" {
		hashName = sys.hashName
	}

	tokenSign, err := sign(priKey, hashName, sessToken)
	if err != nil {
		return erro.Wrap(err)
	}

	// 署名できた。
	log.Debug("signed")

	r.AddCookie(&http.Cookie{Name: cookieTaSess, Value: sess.Value})
	r.Header.Set(headerTaId, taId)
	r.Header.Set(headerTaTokenSig, tokenSign)
	r.Header.Set(headerHashFunc, hashName)
	r.RequestURI = ""

	////////////////////////////////////////////////////////////
	util.LogRequest(r, true)
	////////////////////////////////////////////////////////////
	resp, err = cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()
	////////////////////////////////////////////////////////////
	util.LogResponse(resp, true)
	////////////////////////////////////////////////////////////

	// 認証された。
	log.Debug("authentication finished")

	if resp.Header.Get(headerTaAuthErr) != "" {
		// セッションを保存。
		if _, err := sys.addSession(&session{id: sess.Value, uri: uriBase(r.URL), taId: taId, cli: cli}, expiDate); err != nil {
			return erro.Wrap(err)
		}
	}

	return copyResponse(resp, w)
}

// 相手側 TA の認証開始レスポンスから必要情報を抜き出す。
func parseSession(resp *http.Response) (sess *http.Cookie, sessToken string) {
	for _, cookie := range resp.Cookies() {
		if cookie.Name == cookieTaSess {
			sess = cookie
			break
		}
	}

	return sess, resp.Header.Get(headerTaToken)
}

// 相手側 TA からのレスポンスをリクエスト元へのレスポンスに写す。
func copyResponse(resp *http.Response, w http.ResponseWriter) error {
	// ヘッダフィールドのコピー。
	for key, values := range resp.Header {
		for _, value := range values {
			w.Header().Add(key, value)
		}
	}

	// ステータスのコピー。
	w.WriteHeader(resp.StatusCode)

	// ボディのコピー。
	if _, err := io.Copy(w, resp.Body); err != nil {
		return erro.Wrap(err)
	}

	return nil
}

// 相手側 TA からのお題に署名する。
func sign(priKey *rsa.PrivateKey, hashName, token string) (string, error) {
	hash, err := util.ParseHashFunction(hashName)
	if err != nil {
		return "", erro.Wrap(err)
	}

	h := hash.New()
	h.Write([]byte(token))
	buff, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, h.Sum(nil))
	if err != nil {
		return "", erro.Wrap(err)
	}

	return base64.StdEncoding.EncodeToString(buff), nil
}

// 相手側 TA が提示したセッションの有効期限を読み取る。
func getExpirationDate(sess *http.Cookie) (expiDate time.Time) {
	if sess.MaxAge != 0 {
		return time.Now().Add(time.Duration(sess.MaxAge))
	} else {
		return sess.Expires
	}
}
<commit_msg>真偽判定が逆だったので修正<commit_after>package main

import (
	"crypto/rand"
	"crypto/rsa"
	"encoding/base64"
	"github.com/realglobe-Inc/edo/util"
	"github.com/realglobe-Inc/go-lib-rg/erro"
	"io"
	"net"
	"net/http"
	"net/url"
	"time"
)

const (
	headerTaId       = "X-Edo-Ta-Id"
	headerTaToken    = "X-Edo-Ta-Token"
	headerTaTokenSig = "X-Edo-Ta-Token-Sign"
	headerHashFunc   = "X-Edo-Hash-Function"

	headerTaAuthErr = "X-Edo-Ta-Auth-Error"

	cookieTaSess = "X-Edo-Ta-Session"
)

func uriBase(url *url.URL) string {
	return url.Scheme + "://" + url.Host + url.Path
}

// Web プロキシ。
func proxyApi(sys *system, w http.ResponseWriter, r *http.Request) error {

	taId := r.Header.Get(headerTaId)
	if taId == "" {
		taId = sys.taId
	}

	sess, _, err := sys.session(uriBase(r.URL), taId, nil)
	if err != nil {
		return erro.Wrap(err)
	}

	if sess != nil {
		// セッション確立済み。
		log.Debug("authenticated session is exist")
		return forward(sys, w, r, taId, sess)
	} else {
		// セッション未確立。
		log.Debug("session is not exist")
		return startSession(sys, w, r, taId)
	}
}

// 転送する。
func forward(sys *system, w http.ResponseWriter, r *http.Request, taId string, sess *session) error {
	r.AddCookie(&http.Cookie{Name: cookieTaSess, Value: sess.id})
	r.RequestURI = ""

	////////////////////////////////////////////////////////////
	util.LogRequest(r, true)
	////////////////////////////////////////////////////////////
	resp, err := sess.cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()
	////////////////////////////////////////////////////////////
	util.LogResponse(resp, true)
	////////////////////////////////////////////////////////////

	log.Debug("forwarded")

	if resp.StatusCode == http.StatusUnauthorized && resp.Header.Get(headerTaAuthErr) != "" {
		// edo-auth で 401 Unauthorized なら、タイミングの問題なので startSession からやり直す。
		// 古いセッションは上書きされるので消す必要無し。
		return startSession(sys, w, r, taId)
	}

	return copyResponse(resp, w)
}

// セッション開始。
func startSession(sys *system, w http.ResponseWriter, r *http.Request, taId string) error {

	cli := &http.Client{}

	r.RequestURI = ""

	////////////////////////////////////////////////////////////
	util.LogRequest(r, true)
	////////////////////////////////////////////////////////////
	resp, err := cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()
	////////////////////////////////////////////////////////////
	util.LogResponse(resp, true)
	////////////////////////////////////////////////////////////

	log.Debug("sent raw request")

	if resp.Header.Get(headerTaAuthErr) == "" || resp.StatusCode != http.StatusUnauthorized {
		// 相手側が TA 認証を必要としていなかったのかもしれない。
		return copyResponse(resp, w)
	}

	// 相手側 TA も認証始めた。
	log.Debug("authentication started")

	sess, sessToken := parseSession(resp)
	if sess == nil {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no cookie "+cookieTaSess, nil))
	} else if sessToken == "" {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no header field "+headerTaToken, nil))
	}

	expiDate := getExpirationDate(sess)

	// 認証用データが揃ってた。
	log.Debug("authentication data was found")

	priKey, _, err := sys.privateKey(taId, nil)
	if err != nil {
		return erro.Wrap(err)
	} else if priKey == nil {
		return erro.Wrap(util.NewHttpStatusError(http.StatusForbidden, "no private key of "+taId, nil))
	}

	// 秘密鍵を用意できた。
	log.Debug("private key of " + taId + " is exist")

	hashName := r.Header.Get(headerHashFunc)
	if hashName == "" {
		hashName = sys.hashName
	}

	tokenSign, err := sign(priKey, hashName, sessToken)
	if err != nil {
		return erro.Wrap(err)
	}

	// 署名できた。
	log.Debug("signed")

	r.AddCookie(&http.Cookie{Name: cookieTaSess, Value: sess.Value})
	r.Header.Set(headerTaId, taId)
	r.Header.Set(headerTaTokenSig, tokenSign)
	r.Header.Set(headerHashFunc, hashName)
	r.RequestURI = ""

	////////////////////////////////////////////////////////////
	util.LogRequest(r, true)
	////////////////////////////////////////////////////////////
	resp, err = cli.Do(r)
	if err != nil {
		err = erro.Wrap(err)
		switch erro.Unwrap(err).(type) {
		case *net.OpError:
			return erro.Wrap(util.NewHttpStatusError(http.StatusNotFound, "cannot connect "+uriBase(r.URL), err))
		default:
			return err
		}
	}
	defer resp.Body.Close()
	////////////////////////////////////////////////////////////
	util.LogResponse(resp, true)
	////////////////////////////////////////////////////////////

	// 認証された。
	log.Debug("authentication finished")

	if resp.Header.Get(headerTaAuthErr) == "" {
		// セッションを保存。
		if _, err := sys.addSession(&session{id: sess.Value, uri: uriBase(r.URL), taId: taId, cli: cli}, expiDate); err != nil {
			return erro.Wrap(err)
		}
	}

	return copyResponse(resp, w)
}

// 相手側 TA の認証開始レスポンスから必要情報を抜き出す。
func parseSession(resp *http.Response) (sess *http.Cookie, sessToken string) {
	for _, cookie := range resp.Cookies() {
		if cookie.Name == cookieTaSess {
			sess = cookie
			break
		}
	}

	return sess, resp.Header.Get(headerTaToken)
}

// 相手側 TA からのレスポンスをリクエスト元へのレスポンスに写す。
func copyResponse(resp *http.Response, w http.ResponseWriter) error {
	// ヘッダフィールドのコピー。
	for key, values := range resp.Header {
		for _, value := range values {
			w.Header().Add(key, value)
		}
	}

	// ステータスのコピー。
	w.WriteHeader(resp.StatusCode)

	// ボディのコピー。
	if _, err := io.Copy(w, resp.Body); err != nil {
		return erro.Wrap(err)
	}

	return nil
}

// 相手側 TA からのお題に署名する。
func sign(priKey *rsa.PrivateKey, hashName, token string) (string, error) {
	hash, err := util.ParseHashFunction(hashName)
	if err != nil {
		return "", erro.Wrap(err)
	}

	h := hash.New()
	h.Write([]byte(token))
	buff, err := rsa.SignPKCS1v15(rand.Reader, priKey, hash, h.Sum(nil))
	if err != nil {
		return "", erro.Wrap(err)
	}

	return base64.StdEncoding.EncodeToString(buff), nil
}

// 相手側 TA が提示したセッションの有効期限を読み取る。
func getExpirationDate(sess *http.Cookie) (expiDate time.Time) {
	if sess.MaxAge != 0 {
		return time.Now().Add(time.Duration(sess.MaxAge))
	} else {
		return sess.Expires
	}
}
<|endoftext|>


<commit_before>package circonusgometrics

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"time"

	"github.com/hashicorp/go-retryablehttp"
)

func (m *CirconusMetrics) apiCall(reqMethod string, reqPath string, data []byte) ([]byte, error) {
	dataReader := bytes.NewReader(data)

	// default to SSL
	proto := "https://"
	// allow user to override with explict "http://" in ApiHost
	// if that floats their boat...
	if m.ApiHost[0:4] == "http" {
		proto = ""
	}

	url := fmt.Sprintf("%s%s%s", proto, m.ApiHost, reqPath)

	req, err := retryablehttp.NewRequest(reqMethod, url, dataReader)
	if err != nil {
		return nil, fmt.Errorf("Error making API request to %s %+v", url, err)
	}
	req.Header.Add("Accept", "application/json")
	req.Header.Add("X-Circonus-Auth-Token", m.ApiToken)
	req.Header.Add("X-Circonus-App-Name", m.ApiApp)

	client := retryablehttp.NewClient()
	client.RetryWaitMin = 10 * time.Millisecond
	client.RetryWaitMax = 50 * time.Millisecond
	client.RetryMax = 3
	client.Logger = m.Log

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Error fetching %s: %s\n", url, err)
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Error reading body %+v", err)
	}

	if resp.StatusCode != 200 {
		m.Log.Printf("%+v\n", string(body))

		var response map[string]interface{}
		json.Unmarshal(body, &response)
		if err != nil {
			return nil, fmt.Errorf("Error parsing JSON response %+v", err)
		}
		return nil, fmt.Errorf("Error API response code %+v", response)
	}

	return body, nil
}
<commit_msg>return straight error on fail, reformat creation error, adjust comment<commit_after>package circonusgometrics

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"time"

	"github.com/hashicorp/go-retryablehttp"
)

func (m *CirconusMetrics) apiCall(reqMethod string, reqPath string, data []byte) ([]byte, error) {
	dataReader := bytes.NewReader(data)

	// default to SSL
	proto := "https://"
	// allow override with explict "http://" in ApiHost
	if m.ApiHost[0:4] == "http" {
		proto = ""
	}

	url := fmt.Sprintf("%s%s%s", proto, m.ApiHost, reqPath)

	req, err := retryablehttp.NewRequest(reqMethod, url, dataReader)
	if err != nil {
		return nil, fmt.Errorf("Error creating API request: %s %+v", url, err)
	}
	req.Header.Add("Accept", "application/json")
	req.Header.Add("X-Circonus-Auth-Token", m.ApiToken)
	req.Header.Add("X-Circonus-App-Name", m.ApiApp)

	client := retryablehttp.NewClient()
	client.RetryWaitMin = 10 * time.Millisecond
	client.RetryWaitMax = 50 * time.Millisecond
	client.RetryMax = 3
	client.Logger = m.Log

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Error reading body %+v", err)
	}

	if resp.StatusCode != 200 {
		m.Log.Printf("%+v\n", string(body))

		var response map[string]interface{}
		json.Unmarshal(body, &response)
		if err != nil {
			return nil, fmt.Errorf("Error parsing JSON response %+v", err)
		}
		return nil, fmt.Errorf("Error API response code %+v", response)
	}

	return body, nil
}
<|endoftext|>


<commit_before>package openzwave

//
// Provides a facade for the C++ API that exposes just enough of the underlying C++
// API to be useful to implementing the Ninja Zwave driver
//
// The functions in this module are responsible for marshalling to and from the C functions
// declared in api.hpp and api.cpp.
//

//
// The following #cgo directives assumed that 'go' is a symbolic link that references the gopath that contains the current directory, e.g. ../../../..
//
// All 'go' packages that have this package as a dependency should include such a go link and will then inherit the library built in this package.
//

// #cgo LDFLAGS: -lopenzwave -Lgo/src/github.com/ninjasphere/go-openzwave/openzwave
// #cgo CPPFLAGS: -Iopenzwave/cpp/src/platform -Iopenzwave/cpp/src -Iopenzwave/cpp/src/value_classes
//
// #include "api.h"
import "C"

import (
	"fmt"
	"os"
	"os/signal"
	"time"
	"unsafe"

	"github.com/ninjasphere/go-openzwave/CODE"
	"github.com/ninjasphere/go-openzwave/NT"
	"github.com/ninjasphere/go-openzwave/VT"
)

type api struct {
	options       C.Options // an opaque reference to C++ Options object
	notifications chan Notification
	device        string
	quit          chan bool
	manager       C.Manager
}

//
// The API interface is available to implementors of the EventLoop type.
//
type API interface {
	// notifications are received on this channel
	Notifications() chan Notification

	// free a notification after use.
	FreeNotification(Notification)

	// The EventLoop should return from the function when a signal is received on this channel
	QuitSignal() chan bool
}

// Begin the construction of the API by returning a Configurator
func BuildAPI(configPath string, userPath string, overrides string) Configurator {
	var (
		cConfigPath *C.char = C.CString(configPath)
		cUserPath   *C.char = C.CString(userPath)
		cOverrides  *C.char = C.CString(overrides)
	)
	//defer C.free(unsafe.Pointer(cConfigPath))
	//defer C.free(unsafe.Pointer(cUserPath))
	//defer C.free(unsafe.Pointer(cOverrides))
	return api{
		C.startOptions(cConfigPath, cUserPath, cOverrides),
		make(chan Notification),
		defaultDriverName,
		make(chan bool, 0),
		C.Manager{nil}}
}

// An interface that can be used to configure the API by setting various options,
// and the controller device name. When configuration is finished, call the Run method
// with an EventLoop function. The result of the Run function
// can be passed as a parameter to Os.Exit().
type Configurator interface {
	// Add an integer option.
	AddIntOption(option string, value int) Configurator
	// Add a boolean option.
	AddBoolOption(option string, value bool) Configurator
	// Set the device name used by the driver.
	SetDriver(device string) Configurator
	// Conclude the configuration and start running the supplied event loop. The
	// body of the EventLoop has access to an API reference.
	Run(loop EventLoop) int
}

// Clients of the API should provide an implementation of this type to handle the notifications
// generated by the API.
type EventLoop func(API)

// The type of notifications received via the API's Notifications() channel.
type Notification struct {
	notification *C.Notification
}

// Converts the notification into a string representation.
func (self Notification) String() string {
	return fmt.Sprintf(
		"Notification[\n"+
			"notificationType=%s,\n"+
			"notificationCode=%s,\n"+
			"homeId=0x%08x,\n"+
			"nodeId=0x%02x,\n"+
			"valueType=%s,\n"+
			"valueId=0x%08x]\n",
		NT.ToEnum(int(self.notification.notificationType)),
		CODE.ToEnum(int(self.notification.notificationCode)),
		self.notification.nodeId.homeId,
		self.notification.nodeId.nodeId,
		VT.ToEnum(int(self.notification.valueId.valueType)),
		self.notification.valueId.valueId)
}

// configure the C++ Options object with an integer value
func (self api) AddIntOption(option string, value int) Configurator {
	var cOption *C.char = C.CString(option)
	//defer C.free(unsafe.Pointer(cOption))

	C.addIntOption(self.options, cOption, C.int(value))
	return self
}

// configure the C++ Options object with a boolean value
func (self api) AddBoolOption(option string, value bool) Configurator {
	var cOption *C.char = C.CString(option)

	//defer C.free(unsafe.Pointer(cOption))
	C.addBoolOption(self.options, cOption, C._Bool(value))
	return self
}

// add a driver.
func (self api) SetDriver(device string) Configurator {
	if device != "" {
		self.device = device
	}
	return self
}

//
// Run the supplied event loop
//
// The intent of the complexity is to gracefully handle device insertion and removal events and to
// deal with unexpected (but observed) lockups during the driver removal processing.
//
// The function will only return if a signal is received. It may also call os.Exit(1) in case
// of unexpected lock ups during signal handling, device or driver removal processing.
//
func (self api) Run(loop EventLoop) int {

	// lock the options object, now we are done configuring it

	C.endOptions(self.options)

	// allocate various channels we need

	signals := make(chan os.Signal, 1) // used to receive OS signals
	startQuit := make(chan bool, 2)    // used to indicate we need to quit the event loop
	signalRaised := make(chan bool, 1) // used to indicate to outer loop that it should exit
	exit := make(chan int, 1)          // used to indicate we are ready to exit

	// indicate that we want to wait for these signals

	signal.Notify(signals, os.Interrupt, os.Kill)
	go func() {
		// Block until a signal is received.

		signal := <-signals
		// once we receive a signal, exit of the process is inevitable
		fmt.Printf("received %v signal - commencing shutdown\n", signal)

		startQuit <- true    // try a graceful shutdown of the event loop
		signalRaised <- true // ensure the device existence loop will exit

		// but, just in case this doesn't happen, set up an abort timer.
		time.AfterFunc(time.Second*5, func() {
			fmt.Printf("timed out while waiting for event loop to quit - aborting now\n")
			exit <- 1
		})

		// the user is impatient - just die now
		signal = <-signals
		fmt.Printf("received 2nd %v signal - aborting now\n", signal)
		exit <- 2
	}()

	//
	// This goroutine does the following
	//    starts the manager
	//    starts a device monitoroing loop which
	//       waits for the device to be available
	// 	 starts a device removal goroutine which raises a startQuit signal when removal of the device is detected
	//   	 starts the driver
	//	 starts a go routine that that waits until a startQuit is signaled, then initiates the removal of the driver and quit of the event loop
	//	 runs the event loop
	//
	// It does not exit until either an OS Interrupt or Kill signal is received or driver removal or event loop blocks for some reason.
	//
	// If the device is removed, the monitoring go routine will send a signal via the startQuit channel. The intent is to allow the
	// event loop to exit and have the driver removed.
	//
	// The driver removal goroutine waits for the startQuit signal, then attempts to remove the driver. If this completes successfully
	// it propagates a quit signal to the event loop. It also sets up an abort timer which will exit the process if either
	// the driver removal or quit signal propagation blocks for some reason.
	//
	// If an OS signal is received, the main go routine will send signals to the startQuit and to the signalRaised channels.
	// It then waits for another signal, for the outer loop to exit or for a 5 second timeout. When one of these occurs, the
	// process will exit.
	//

	go func() {
		cSelf := unsafe.Pointer(&self) // a reference to self

		self.manager = C.startManager(cSelf) // start the manager
		defer C.stopManager(self.manager, cSelf)

		cDevice := C.CString(self.device) // allocate a C string for device
		defer C.free(unsafe.Pointer(cDevice))

		// a function which returns true if the device exists
		deviceExists := func() bool {
			if _, err := os.Stat(self.device); err == nil {
				return true
			} else {
				if os.IsNotExist(err) {
					return false
				} else {
					return true
				}
			}
		}

		// waits until the state matches the desired state.
		pollUntilDeviceExistsStateEquals := func(comparand bool) {
			for deviceExists() != comparand {
				time.Sleep(time.Second)
			}
		}

		// there is one iteration of this loop for each device insertion/removal cycle
		done := false
		for !done {
			select {
			case done = <-signalRaised: // we received a signal, allow us to quit
				break
			default:
				// one iteration of a device insert/removal cycle

				// wait until device present
				fmt.Printf("waiting until %s is available\n", self.device)
				pollUntilDeviceExistsStateEquals(true)

				go func() {

					// wait until device absent
					pollUntilDeviceExistsStateEquals(false)
					fmt.Printf("device %s removed\n", self.device)

					// start the removal of the driver
					startQuit <- true
				}()

				C.addDriver(self.manager, cDevice)

				go func() {
					// wait until something (OS signal handler or device existence monitor) decides we need to terminate
					<-startQuit

					// we start an abort timer, because if the driver blocks, we need to start the driver process
					abortTimer := time.AfterFunc(5*time.Second, func() {
						fmt.Printf("failed to remove driver - exiting driver process\n")
						exit <- 3
					})

					// try to remove the driver
					if C.removeDriver(self.manager, cDevice) {
						self.quit <- true
						abortTimer.Stop() // if we get to here in a timely fashion we can stop the abort timer
					} else {
						// this is unexpected, if we get to here, let the abort timer do its thing
						fmt.Printf("removeDriver call failed - waiting for abort\n")
					}
				}()

				loop(self) // run the event loop
			}
		}

		exit <- 4
	}()

	return <-exit
}

func (self api) Notifications() chan Notification {
	return self.notifications
}

func (self api) QuitSignal() chan bool {
	return self.quit
}

func (self api) FreeNotification(apiNotification Notification) {
	C.freeNotification(apiNotification.notification)
}

//export onNotificationWrapper
func onNotificationWrapper(notification *C.Notification, context unsafe.Pointer) {
	self := (*api)(context)
	self.notifications <- Notification{notification}
}
<commit_msg>Replace chan bool with chan struct{} since we care about synchronization, not values.<commit_after>package openzwave

//
// Provides a facade for the C++ API that exposes just enough of the underlying C++
// API to be useful to implementing the Ninja Zwave driver
//
// The functions in this module are responsible for marshalling to and from the C functions
// declared in api.hpp and api.cpp.
//

//
// The following #cgo directives assumed that 'go' is a symbolic link that references the gopath that contains the current directory, e.g. ../../../..
//
// All 'go' packages that have this package as a dependency should include such a go link and will then inherit the library built in this package.
//

// #cgo LDFLAGS: -lopenzwave -Lgo/src/github.com/ninjasphere/go-openzwave/openzwave
// #cgo CPPFLAGS: -Iopenzwave/cpp/src/platform -Iopenzwave/cpp/src -Iopenzwave/cpp/src/value_classes
//
// #include "api.h"
import "C"

import (
	"fmt"
	"os"
	"os/signal"
	"time"
	"unsafe"

	"github.com/ninjasphere/go-openzwave/CODE"
	"github.com/ninjasphere/go-openzwave/NT"
	"github.com/ninjasphere/go-openzwave/VT"
)

type api struct {
	options       C.Options // an opaque reference to C++ Options object
	notifications chan Notification
	device        string
	quit          chan struct{}
	manager       C.Manager
}

//
// The API interface is available to implementors of the EventLoop type.
//
type API interface {
	// notifications are received on this channel
	Notifications() chan Notification

	// free a notification after use.
	FreeNotification(Notification)

	// The EventLoop should return from the function when a signal is received on this channel
	QuitSignal() chan struct{}
}

// Begin the construction of the API by returning a Configurator
func BuildAPI(configPath string, userPath string, overrides string) Configurator {
	var (
		cConfigPath *C.char = C.CString(configPath)
		cUserPath   *C.char = C.CString(userPath)
		cOverrides  *C.char = C.CString(overrides)
	)
	//defer C.free(unsafe.Pointer(cConfigPath))
	//defer C.free(unsafe.Pointer(cUserPath))
	//defer C.free(unsafe.Pointer(cOverrides))
	return api{
		C.startOptions(cConfigPath, cUserPath, cOverrides),
		make(chan Notification),
		defaultDriverName,
		make(chan struct{}, 0),
		C.Manager{nil}}
}

// An interface that can be used to configure the API by setting various options,
// and the controller device name. When configuration is finished, call the Run method
// with an EventLoop function. The result of the Run function
// can be passed as a parameter to Os.Exit().
type Configurator interface {
	// Add an integer option.
	AddIntOption(option string, value int) Configurator
	// Add a boolean option.
	AddBoolOption(option string, value bool) Configurator
	// Set the device name used by the driver.
	SetDriver(device string) Configurator
	// Conclude the configuration and start running the supplied event loop. The
	// body of the EventLoop has access to an API reference.
	Run(loop EventLoop) int
}

// Clients of the API should provide an implementation of this type to handle the notifications
// generated by the API.
type EventLoop func(API)

// The type of notifications received via the API's Notifications() channel.
type Notification struct {
	notification *C.Notification
}

// Converts the notification into a string representation.
func (self Notification) String() string {
	return fmt.Sprintf(
		"Notification[\n"+
			"notificationType=%s,\n"+
			"notificationCode=%s,\n"+
			"homeId=0x%08x,\n"+
			"nodeId=0x%02x,\n"+
			"valueType=%s,\n"+
			"valueId=0x%08x]\n",
		NT.ToEnum(int(self.notification.notificationType)),
		CODE.ToEnum(int(self.notification.notificationCode)),
		self.notification.nodeId.homeId,
		self.notification.nodeId.nodeId,
		VT.ToEnum(int(self.notification.valueId.valueType)),
		self.notification.valueId.valueId)
}

// configure the C++ Options object with an integer value
func (self api) AddIntOption(option string, value int) Configurator {
	var cOption *C.char = C.CString(option)
	//defer C.free(unsafe.Pointer(cOption))

	C.addIntOption(self.options, cOption, C.int(value))
	return self
}

// configure the C++ Options object with a boolean value
func (self api) AddBoolOption(option string, value bool) Configurator {
	var cOption *C.char = C.CString(option)

	//defer C.free(unsafe.Pointer(cOption))
	C.addBoolOption(self.options, cOption, C._Bool(value))
	return self
}

// add a driver.
func (self api) SetDriver(device string) Configurator {
	if device != "" {
		self.device = device
	}
	return self
}

//
// Run the supplied event loop
//
// The intent of the complexity is to gracefully handle device insertion and removal events and to
// deal with unexpected (but observed) lockups during the driver removal processing.
//
// The function will only return if a signal is received. It may also call os.Exit(1) in case
// of unexpected lock ups during signal handling, device or driver removal processing.
//
func (self api) Run(loop EventLoop) int {

	// lock the options object, now we are done configuring it

	C.endOptions(self.options)

	// allocate various channels we need

	signals := make(chan os.Signal, 1) // used to receive OS signals
	startQuit := make(chan struct{}, 2)    // used to indicate we need to quit the event loop
	quitDeviceMonitor := make(chan struct{}, 1) // used to indicate to outer loop that it should exit
	exit := make(chan int, 1)          // used to indicate we are ready to exit

	// indicate that we want to wait for these signals

	signal.Notify(signals, os.Interrupt, os.Kill)
	go func() {
		// Block until a signal is received.

		signal := <-signals
		// once we receive a signal, exit of the process is inevitable
		fmt.Printf("received %v signal - commencing shutdown\n", signal)

		// try a graceful shutdown of the event loop
		startQuit <- struct{}{}
		// and the device monitor loop
		quitDeviceMonitor <- struct{}{}

		// but, just in case this doesn't happen, set up an abort timer.
		time.AfterFunc(time.Second*5, func() {
			fmt.Printf("timed out while waiting for event loop to quit - aborting now\n")
			exit <- 1
		})

		// the user is impatient - just die now
		signal = <-signals
		fmt.Printf("received 2nd %v signal - aborting now\n", signal)
		exit <- 2
	}()

	//
	// This goroutine does the following
	//    starts the manager
	//    starts a device monitoroing loop which
	//       waits for the device to be available
	// 	 starts a device removal goroutine which raises a startQuit signal when removal of the device is detected
	//   	 starts the driver
	//	 starts a go routine that that waits until a startQuit is signaled, then initiates the removal of the driver and quit of the event loop
	//	 runs the event loop
	//
	// It does not exit until either an OS Interrupt or Kill signal is received or driver removal or event loop blocks for some reason.
	//
	// If the device is removed, the monitoring go routine will send a signal via the startQuit channel. The intent is to allow the
	// event loop to exit and have the driver removed.
	//
	// The driver removal goroutine waits for the startQuit signal, then attempts to remove the driver. If this completes successfully
	// it propagates a quit signal to the event loop. It also sets up an abort timer which will exit the process if either
	// the driver removal or quit signal propagation blocks for some reason.
	//
	// If an OS signal is received, the main go routine will send signals to the startQuit and to the quitDeviceMonitor channels.
	// It then waits for another signal, for the outer loop to exit or for a 5 second timeout. When one of these occurs, the
	// process will exit.
	//

	go func() {
		cSelf := unsafe.Pointer(&self) // a reference to self

		self.manager = C.startManager(cSelf) // start the manager
		defer C.stopManager(self.manager, cSelf)

		cDevice := C.CString(self.device) // allocate a C string for device
		defer C.free(unsafe.Pointer(cDevice))

		// a function which returns true if the device exists
		deviceExists := func() bool {
			if _, err := os.Stat(self.device); err == nil {
				return true
			} else {
				if os.IsNotExist(err) {
					return false
				} else {
					return true
				}
			}
		}

		// waits until the state matches the desired state.
		pollUntilDeviceExistsStateEquals := func(comparand bool) {
			for deviceExists() != comparand {
				time.Sleep(time.Second)
			}
		}

		// there is one iteration of this loop for each device insertion/removal cycle
		done := false
		for !done {
			select {
			case doneSignal := <-quitDeviceMonitor: // we received a signal, allow us to quit
			     _= doneSignal;
			     done = true
			default:
				// one iteration of a device insert/removal cycle

				// wait until device present
				fmt.Printf("waiting until %s is available\n", self.device)
				pollUntilDeviceExistsStateEquals(true)

				go func() {

					// wait until device absent
					pollUntilDeviceExistsStateEquals(false)
					fmt.Printf("device %s removed\n", self.device)

					// start the removal of the driver
					startQuit <- struct{}{}
				}()

				C.addDriver(self.manager, cDevice)

				go func() {
					// wait until something (OS signal handler or device existence monitor) decides we need to terminate
					<-startQuit

					// we start an abort timer, because if the driver blocks, we need to start the driver process
					abortTimer := time.AfterFunc(5*time.Second, func() {
						fmt.Printf("failed to remove driver - exiting driver process\n")
						exit <- 3
					})

					// try to remove the driver
					if C.removeDriver(self.manager, cDevice) {
						self.quit <- struct{}{}
						abortTimer.Stop() // if we get to here in a timely fashion we can stop the abort timer
					} else {
						// this is unexpected, if we get to here, let the abort timer do its thing
						fmt.Printf("removeDriver call failed - waiting for abort\n")
					}
				}()

				loop(self) // run the event loop
			}
		}

		exit <- 4
	}()

	return <-exit
}

func (self api) Notifications() chan Notification {
	return self.notifications
}

func (self api) QuitSignal() chan struct{} {
	return self.quit
}

func (self api) FreeNotification(apiNotification Notification) {
	C.freeNotification(apiNotification.notification)
}

//export onNotificationWrapper
func onNotificationWrapper(notification *C.Notification, context unsafe.Pointer) {
	self := (*api)(context)
	self.notifications <- Notification{notification}
}
<|endoftext|>


<commit_before>package ghapi

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

// APIInfo contains the API URL and Authtoken for making API requests. APIInfo is embedded in all other API structs.
type APIInfo struct {
	BaseURL     string
	OAuth2Token string
}

// RepositoryInfo contains APIInfo, the repository owner, and the repository name.
type RepositoryInfo struct {
	APIInfo
	Owner      string
	Repository string
}

// GitHubAPI is returned by the NewGitHubAPI function and contains fields for making API calls.
type GitHubAPI struct {
	APIInfo
	Issue        IssueAPI
	User         UserAPI
	Organization OrganizationAPI
	PullRequest  PullRequestsAPI
	Status       StatusAPI
	Branch       BranchesAPI
	Repository   RepositoryAPI
	Contents     ContentsAPI
}

// IssueAPI is used to get information about a repository's issues. Note Pull Requests are treated as issues in some
// cases, for example labeling a Pull Request is done through IssueAPI.
type IssueAPI struct {
	RepositoryInfo
}

// UserAPI is used to get information about a user.
type UserAPI struct {
	APIInfo
}

// OrganizationAPI is used to get information about an organization.
type OrganizationAPI struct {
	APIInfo
	Organization string
}

// PullRequestsAPI is used to get information about a repository's pull requests.
type PullRequestsAPI struct {
	RepositoryInfo
}

// StatusAPI is used to get commit status information in a repository.
type StatusAPI struct {
	RepositoryInfo
}

// BranchesAPI is used to get information about a repository's branches.
type BranchesAPI struct {
	RepositoryInfo
}

// RepositoryAPI is used to get information about a repository.
type RepositoryAPI struct {
	RepositoryInfo
}

// ContentsAPI is used to get information about the contents of a file in a repository.
type ContentsAPI struct {
	RepositoryInfo
}

// AuthenticatedUser contains information about the current authenticated user.
type AuthenticatedUser struct {
	Login             string    `json:"login"`
	ID                int       `json:"id"`
	AvatarURL         string    `json:"avatar_url"`
	GravatarID        string    `json:"gravatar_id"`
	URL               string    `json:"url"`
	HTMLURL           string    `json:"html_url"`
	FollowersURL      string    `json:"followers_url"`
	FollowingURL      string    `json:"following_url"`
	GistsURL          string    `json:"gists_url"`
	StarredURL        string    `json:"starred_url"`
	SubscriptionsURL  string    `json:"subscriptions_url"`
	OrganizationsURL  string    `json:"organizations_url"`
	ReposURL          string    `json:"repos_url"`
	EventsURL         string    `json:"events_url"`
	ReceivedEventsURL string    `json:"received_events_url"`
	Type              string    `json:"type"`
	SiteAdmin         bool      `json:"site_admin"`
	Name              string    `json:"name"`
	Company           string    `json:"company"`
	Blog              string    `json:"blog"`
	Location          string    `json:"location"`
	Email             string    `json:"email"`
	Hireable          bool      `json:"hireable"`
	Bio               string    `json:"bio"`
	PublicRepos       int       `json:"public_repos"`
	PublicGists       int       `json:"public_gists"`
	Followers         int       `json:"followers"`
	Following         int       `json:"following"`
	CreatedAt         time.Time `json:"created_at"`
	UpdatedAt         time.Time `json:"updated_at"`
	TotalPrivateRepos int       `json:"total_private_repos"`
	OwnedPrivateRepos int       `json:"owned_private_repos"`
	PrivateGists      int       `json:"private_gists"`
	DiskUsage         int       `json:"disk_usage"`
	Collaborators     int       `json:"collaborators"`
	Plan              struct {
		Name          string `json:"name"`
		Space         int    `json:"space"`
		PrivateRepos  int    `json:"private_repos"`
		Collaborators int    `json:"collaborators"`
	} `json:"plan"`
}

// NewGitHubAPI returns a new GitHubAPI using the specified repository and authentication information.
func NewGitHubAPI(baseURL, owner, repository, authToken string) GitHubAPI {
	apiInfo := APIInfo{BaseURL: baseURL, OAuth2Token: authToken}

	gitHubAPI := GitHubAPI{APIInfo: apiInfo}

	repositoryInfo := RepositoryInfo{
		APIInfo:    apiInfo,
		Owner:      owner,
		Repository: repository,
	}

	gitHubAPI.Issue = IssueAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.User = UserAPI{APIInfo: apiInfo}
	gitHubAPI.Organization = OrganizationAPI{APIInfo: apiInfo, Organization: owner}
	gitHubAPI.PullRequest = PullRequestsAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.Status = StatusAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.Branch = BranchesAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.Repository = RepositoryAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.Contents = ContentsAPI{RepositoryInfo: repositoryInfo}

	return gitHubAPI
}

// GetUser returns the current authenticated user.
func GetUser(baseURL, authToken string) (*AuthenticatedUser, error) {
	apiInfo := APIInfo{BaseURL: baseURL, OAuth2Token: authToken}
	url := apiInfo.addBaseURL("/user")
	resp, err := apiInfo.httpGet(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var user AuthenticatedUser

	j := json.NewDecoder(resp.Body)
	if err = j.Decode(&user); err != nil {
		return nil, err
	}

	return &user, nil
}

// OrgSummary contains organization summary information.
type OrgSummary struct {
	Login            string `json:"login"`
	ID               int    `json:"id"`
	URL              string `json:"url"`
	ReposURL         string `json:"repos_url"`
	EventsURL        string `json:"events_url"`
	HooksURL         string `json:"hooks_url"`
	IssuesURL        string `json:"issues_url"`
	MembersURL       string `json:"members_url"`
	PublicMembersURL string `json:"public_members_url"`
	AvatarURL        string `json:"avatar_url"`
	Description      string `json:"description"`
}

// GetOrganizations returns Organization summary information from /organizations.
//
// See https://developer.github.com/v3/orgs/#list-all-organizations.
func GetOrganizations(baseURL, authToken string, since int) ([]OrgSummary, error) {
	apiInfo := APIInfo{BaseURL: baseURL, OAuth2Token: authToken}
	url := apiInfo.addBaseURL(fmt.Sprintf("/organizations?since=%d", since))
	resp, err := apiInfo.httpGet(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var orgs []OrgSummary

	j := json.NewDecoder(resp.Body)
	if err = j.Decode(&orgs); err != nil {
		return nil, err
	}

	return orgs, nil
}

// Is404 returns true if the error is an HTTP 404.
func Is404(err error) bool {
	return IsHTTPError(err, 404)
}

// IsHTTPError returns true if the error is an HTTP error with the specified status code.
func IsHTTPError(err error, statusCode int) bool {
	if err == nil {
		return false
	}
	switch val := err.(type) {
	case *ErrHTTPError:
		if val.StatusCode == statusCode {
			return true
		}
	}
	return false
}

func (apiInfo *APIInfo) addBaseURL(url string) string {
	return apiInfo.BaseURL + url
}

func (apiInfo *RepositoryInfo) getURL(url string) string {
	url = strings.Replace(url, ":owner", apiInfo.Owner, 1)
	url = strings.Replace(url, ":repo", apiInfo.Repository, 1)
	return apiInfo.addBaseURL(url)
}

func (apiInfo *APIInfo) getHTTPRequest(method, url string, body *string) (*http.Request, error) {
	var bodyReader io.Reader
	if body != nil {
		bodyReader = strings.NewReader(*body)
	}
	req, err := http.NewRequest(method, url, bodyReader)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Accept", "application/vnd.github.v3+json")
	req.Header.Add("Content-Type", "application/json")
	if apiInfo.OAuth2Token != "" {
		req.Header.Add("Authorization", fmt.Sprintf("token %s", apiInfo.OAuth2Token))
	}

	return req, nil
}

func (apiInfo *APIInfo) doHTTPRequest(method, url string, body *string, acceptHeader string) (*http.Response, error) {
	req, err := apiInfo.getHTTPRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	if acceptHeader != "" {
		req.Header.Set("Accept", acceptHeader)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode >= 300 {
		defer resp.Body.Close()
		var requestBody, responseBody string
		b, err := ioutil.ReadAll(resp.Body)
		if err == nil {
			responseBody = string(b)
		}
		if body != nil {
			requestBody = *body
		}
		err = &ErrHTTPError{
			Status:       resp.Status,
			StatusCode:   resp.StatusCode,
			Method:       method,
			URL:          url,
			RequestBody:  requestBody,
			ResponseBody: responseBody,
		}
		return nil, err
	}

	return resp, nil
}

func (apiInfo *APIInfo) httpDelete(url string) (*http.Response, error) {
	return apiInfo.doHTTPRequest("DELETE", url, nil, "")
}

func (apiInfo *APIInfo) httpGet(url string) (*http.Response, error) {
	return apiInfo.doHTTPRequest("GET", url, nil, "")
}

func (apiInfo *APIInfo) httpPatch(url, body string) (*http.Response, error) {
	return apiInfo.doHTTPRequest("PATCH", url, &body, "")
}

func (apiInfo *APIInfo) httpPost(url, body string) (*http.Response, error) {
	return apiInfo.doHTTPRequest("POST", url, &body, "")
}
<commit_msg>add GetOAuthScopes<commit_after>package ghapi

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

// APIInfo contains the API URL and Authtoken for making API requests. APIInfo is embedded in all other API structs.
type APIInfo struct {
	BaseURL     string
	OAuth2Token string
}

// RepositoryInfo contains APIInfo, the repository owner, and the repository name.
type RepositoryInfo struct {
	APIInfo
	Owner      string
	Repository string
}

// GitHubAPI is returned by the NewGitHubAPI function and contains fields for making API calls.
type GitHubAPI struct {
	APIInfo
	Issue        IssueAPI
	User         UserAPI
	Organization OrganizationAPI
	PullRequest  PullRequestsAPI
	Status       StatusAPI
	Branch       BranchesAPI
	Repository   RepositoryAPI
	Contents     ContentsAPI
}

// IssueAPI is used to get information about a repository's issues. Note Pull Requests are treated as issues in some
// cases, for example labeling a Pull Request is done through IssueAPI.
type IssueAPI struct {
	RepositoryInfo
}

// UserAPI is used to get information about a user.
type UserAPI struct {
	APIInfo
}

// OrganizationAPI is used to get information about an organization.
type OrganizationAPI struct {
	APIInfo
	Organization string
}

// PullRequestsAPI is used to get information about a repository's pull requests.
type PullRequestsAPI struct {
	RepositoryInfo
}

// StatusAPI is used to get commit status information in a repository.
type StatusAPI struct {
	RepositoryInfo
}

// BranchesAPI is used to get information about a repository's branches.
type BranchesAPI struct {
	RepositoryInfo
}

// RepositoryAPI is used to get information about a repository.
type RepositoryAPI struct {
	RepositoryInfo
}

// ContentsAPI is used to get information about the contents of a file in a repository.
type ContentsAPI struct {
	RepositoryInfo
}

// AuthenticatedUser contains information about the current authenticated user.
type AuthenticatedUser struct {
	Login             string    `json:"login"`
	ID                int       `json:"id"`
	AvatarURL         string    `json:"avatar_url"`
	GravatarID        string    `json:"gravatar_id"`
	URL               string    `json:"url"`
	HTMLURL           string    `json:"html_url"`
	FollowersURL      string    `json:"followers_url"`
	FollowingURL      string    `json:"following_url"`
	GistsURL          string    `json:"gists_url"`
	StarredURL        string    `json:"starred_url"`
	SubscriptionsURL  string    `json:"subscriptions_url"`
	OrganizationsURL  string    `json:"organizations_url"`
	ReposURL          string    `json:"repos_url"`
	EventsURL         string    `json:"events_url"`
	ReceivedEventsURL string    `json:"received_events_url"`
	Type              string    `json:"type"`
	SiteAdmin         bool      `json:"site_admin"`
	Name              string    `json:"name"`
	Company           string    `json:"company"`
	Blog              string    `json:"blog"`
	Location          string    `json:"location"`
	Email             string    `json:"email"`
	Hireable          bool      `json:"hireable"`
	Bio               string    `json:"bio"`
	PublicRepos       int       `json:"public_repos"`
	PublicGists       int       `json:"public_gists"`
	Followers         int       `json:"followers"`
	Following         int       `json:"following"`
	CreatedAt         time.Time `json:"created_at"`
	UpdatedAt         time.Time `json:"updated_at"`
	TotalPrivateRepos int       `json:"total_private_repos"`
	OwnedPrivateRepos int       `json:"owned_private_repos"`
	PrivateGists      int       `json:"private_gists"`
	DiskUsage         int       `json:"disk_usage"`
	Collaborators     int       `json:"collaborators"`
	Plan              struct {
		Name          string `json:"name"`
		Space         int    `json:"space"`
		PrivateRepos  int    `json:"private_repos"`
		Collaborators int    `json:"collaborators"`
	} `json:"plan"`
}

// NewGitHubAPI returns a new GitHubAPI using the specified repository and authentication information.
func NewGitHubAPI(baseURL, owner, repository, authToken string) GitHubAPI {
	apiInfo := APIInfo{BaseURL: baseURL, OAuth2Token: authToken}

	gitHubAPI := GitHubAPI{APIInfo: apiInfo}

	repositoryInfo := RepositoryInfo{
		APIInfo:    apiInfo,
		Owner:      owner,
		Repository: repository,
	}

	gitHubAPI.Issue = IssueAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.User = UserAPI{APIInfo: apiInfo}
	gitHubAPI.Organization = OrganizationAPI{APIInfo: apiInfo, Organization: owner}
	gitHubAPI.PullRequest = PullRequestsAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.Status = StatusAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.Branch = BranchesAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.Repository = RepositoryAPI{RepositoryInfo: repositoryInfo}
	gitHubAPI.Contents = ContentsAPI{RepositoryInfo: repositoryInfo}

	return gitHubAPI
}

// GetUser returns the current authenticated user.
func GetUser(baseURL, authToken string) (*AuthenticatedUser, error) {
	apiInfo := APIInfo{BaseURL: baseURL, OAuth2Token: authToken}
	url := apiInfo.addBaseURL("/user")
	resp, err := apiInfo.httpGet(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var user AuthenticatedUser

	j := json.NewDecoder(resp.Body)
	if err = j.Decode(&user); err != nil {
		return nil, err
	}

	return &user, nil
}

// GetOAuthScopes returns the current authenticated user's OAuth scopes.
func GetOAuthScopes(baseURL, authToken string) ([]string, error) {
	apiInfo := APIInfo{BaseURL: baseURL, OAuth2Token: authToken}
	url := apiInfo.addBaseURL("/user")
	resp, err := apiInfo.httpGet(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if _, err = io.Copy(ioutil.Discard, resp.Body); err != nil {
		return nil, err
	}

	scopesHeader := resp.Header.Get("x-oauth-scopes")
	var scopes []string
	for _, s := range strings.Split(scopesHeader, ",") {
		s = strings.TrimSpace(s)
		if s != "" {
			scopes = append(scopes, s)
		}
	}

	return scopes, nil
}

// OrgSummary contains organization summary information.
type OrgSummary struct {
	Login            string `json:"login"`
	ID               int    `json:"id"`
	URL              string `json:"url"`
	ReposURL         string `json:"repos_url"`
	EventsURL        string `json:"events_url"`
	HooksURL         string `json:"hooks_url"`
	IssuesURL        string `json:"issues_url"`
	MembersURL       string `json:"members_url"`
	PublicMembersURL string `json:"public_members_url"`
	AvatarURL        string `json:"avatar_url"`
	Description      string `json:"description"`
}

// GetOrganizations returns Organization summary information from /organizations.
//
// See https://developer.github.com/v3/orgs/#list-all-organizations.
func GetOrganizations(baseURL, authToken string, since int) ([]OrgSummary, error) {
	apiInfo := APIInfo{BaseURL: baseURL, OAuth2Token: authToken}
	url := apiInfo.addBaseURL(fmt.Sprintf("/organizations?since=%d", since))
	resp, err := apiInfo.httpGet(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var orgs []OrgSummary

	j := json.NewDecoder(resp.Body)
	if err = j.Decode(&orgs); err != nil {
		return nil, err
	}

	return orgs, nil
}

// Is404 returns true if the error is an HTTP 404.
func Is404(err error) bool {
	return IsHTTPError(err, 404)
}

// IsHTTPError returns true if the error is an HTTP error with the specified status code.
func IsHTTPError(err error, statusCode int) bool {
	if err == nil {
		return false
	}
	switch val := err.(type) {
	case *ErrHTTPError:
		if val.StatusCode == statusCode {
			return true
		}
	}
	return false
}

func (apiInfo *APIInfo) addBaseURL(url string) string {
	return apiInfo.BaseURL + url
}

func (apiInfo *RepositoryInfo) getURL(url string) string {
	url = strings.Replace(url, ":owner", apiInfo.Owner, 1)
	url = strings.Replace(url, ":repo", apiInfo.Repository, 1)
	return apiInfo.addBaseURL(url)
}

func (apiInfo *APIInfo) getHTTPRequest(method, url string, body *string) (*http.Request, error) {
	var bodyReader io.Reader
	if body != nil {
		bodyReader = strings.NewReader(*body)
	}
	req, err := http.NewRequest(method, url, bodyReader)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Accept", "application/vnd.github.v3+json")
	req.Header.Add("Content-Type", "application/json")
	if apiInfo.OAuth2Token != "" {
		req.Header.Add("Authorization", fmt.Sprintf("token %s", apiInfo.OAuth2Token))
	}

	return req, nil
}

func (apiInfo *APIInfo) doHTTPRequest(method, url string, body *string, acceptHeader string) (*http.Response, error) {
	req, err := apiInfo.getHTTPRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	if acceptHeader != "" {
		req.Header.Set("Accept", acceptHeader)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode >= 300 {
		defer resp.Body.Close()
		var requestBody, responseBody string
		b, err := ioutil.ReadAll(resp.Body)
		if err == nil {
			responseBody = string(b)
		}
		if body != nil {
			requestBody = *body
		}
		err = &ErrHTTPError{
			Status:       resp.Status,
			StatusCode:   resp.StatusCode,
			Method:       method,
			URL:          url,
			RequestBody:  requestBody,
			ResponseBody: responseBody,
		}
		return nil, err
	}

	return resp, nil
}

func (apiInfo *APIInfo) httpDelete(url string) (*http.Response, error) {
	return apiInfo.doHTTPRequest("DELETE", url, nil, "")
}

func (apiInfo *APIInfo) httpGet(url string) (*http.Response, error) {
	return apiInfo.doHTTPRequest("GET", url, nil, "")
}

func (apiInfo *APIInfo) httpPatch(url, body string) (*http.Response, error) {
	return apiInfo.doHTTPRequest("PATCH", url, &body, "")
}

func (apiInfo *APIInfo) httpPost(url, body string) (*http.Response, error) {
	return apiInfo.doHTTPRequest("POST", url, &body, "")
}
<|endoftext|>