github.com/cyverse/go-irodsclient@v0.13.2/irods/connection/request_response.go (about)

     1  package connection
     2  
     3  import (
     4  	"github.com/cyverse/go-irodsclient/irods/common"
     5  	"github.com/cyverse/go-irodsclient/irods/message"
     6  	"golang.org/x/xerrors"
     7  )
     8  
     9  // Request is an interface for calling iRODS RPC.
    10  type Request interface {
    11  	GetMessage() (*message.IRODSMessage, error)
    12  }
    13  
    14  // Response is an interface for response of iRODS RPC Call.
    15  type Response interface {
    16  	FromMessage(*message.IRODSMessage) error
    17  }
    18  
    19  // CheckErrorResponse is a Response on which CheckError can be called.
    20  type CheckErrorResponse interface {
    21  	Response
    22  	CheckError() error
    23  }
    24  
    25  // RequestResponsePair is a structure that wraps Request, Response, and other parameters for making iRODS RPC call.
    26  type RequestResponsePair struct {
    27  	Request          Request
    28  	Response         Response
    29  	BsBuffer         []byte                 // can be null
    30  	RequestCallback  common.TrackerCallBack // can be null
    31  	ResponseCallback common.TrackerCallBack // can be null
    32  	Error            error
    33  }
    34  
    35  // Request sends a request and expects a response.
    36  // bsBuffer is optional
    37  func (conn *IRODSConnection) Request(request Request, response Response, bsBuffer []byte) error {
    38  	return conn.RequestWithTrackerCallBack(request, response, bsBuffer, nil, nil)
    39  }
    40  
    41  // RequestWithTrackerCallBack sends a request and expects a response.
    42  // bsBuffer is optional
    43  func (conn *IRODSConnection) RequestWithTrackerCallBack(request Request, response Response, bsBuffer []byte, reqCallback common.TrackerCallBack, resCallback common.TrackerCallBack) error {
    44  	// set transaction dirty
    45  	conn.SetTransactionDirty(true)
    46  
    47  	requestMessage, err := conn.getRequestMessage(request, true, false)
    48  	if err != nil {
    49  		if conn.metrics != nil {
    50  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
    51  		}
    52  		return err
    53  	}
    54  
    55  	err = conn.SendMessageWithTrackerCallBack(requestMessage, reqCallback)
    56  	if err != nil {
    57  		if conn.metrics != nil {
    58  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
    59  		}
    60  		return xerrors.Errorf("failed to send a request message: %w", err)
    61  	}
    62  
    63  	// Server responds with results
    64  	// external bs buffer
    65  	responseMessage, err := conn.ReadMessageWithTrackerCallBack(bsBuffer, resCallback)
    66  	if err != nil {
    67  		if conn.metrics != nil {
    68  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
    69  		}
    70  		return xerrors.Errorf("failed to receive a response message: %w", err)
    71  	}
    72  
    73  	err = conn.getResponse(responseMessage, response, true)
    74  	if err != nil {
    75  		if conn.metrics != nil {
    76  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
    77  		}
    78  		return xerrors.Errorf("failed to parse response message: %w", err)
    79  	}
    80  
    81  	return nil
    82  }
    83  
    84  // RequestAsyncWithTrackerCallBack sends multiple requests and expects responses.
    85  func (conn *IRODSConnection) RequestAsyncWithTrackerCallBack(rrChan chan RequestResponsePair) chan RequestResponsePair {
    86  	waitResponseChan := make(chan RequestResponsePair, 100)
    87  	outputPair := make(chan RequestResponsePair, 100)
    88  
    89  	var lastErr error
    90  
    91  	// sender
    92  	go func() {
    93  		for {
    94  			pair, ok := <-rrChan
    95  			if !ok {
    96  				// input closed
    97  				close(waitResponseChan)
    98  				break
    99  			}
   100  
   101  			// if errored before? skip
   102  			if lastErr != nil {
   103  				pair.Error = lastErr
   104  				waitResponseChan <- pair
   105  				continue
   106  			}
   107  
   108  			requestMessage, err := conn.getRequestMessage(pair.Request, true, false)
   109  			if err != nil {
   110  				if conn.metrics != nil {
   111  					conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   112  				}
   113  
   114  				lastErr = err
   115  				pair.Error = lastErr
   116  				waitResponseChan <- pair
   117  				continue
   118  			}
   119  
   120  			err = conn.SendMessageWithTrackerCallBack(requestMessage, pair.RequestCallback)
   121  			if err != nil {
   122  				if conn.metrics != nil {
   123  					conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   124  				}
   125  
   126  				lastErr = xerrors.Errorf("failed to send a request message: %w", err)
   127  				pair.Error = lastErr
   128  				waitResponseChan <- pair
   129  				continue
   130  			}
   131  
   132  			waitResponseChan <- pair
   133  		}
   134  	}()
   135  
   136  	// receiver
   137  	go func() {
   138  		for {
   139  			pair, ok := <-waitResponseChan
   140  			if !ok {
   141  				// input closed
   142  				close(outputPair)
   143  				break
   144  			}
   145  
   146  			// if errored before? skip
   147  			if lastErr != nil {
   148  				if pair.Error == nil {
   149  					pair.Error = lastErr
   150  				}
   151  				outputPair <- pair
   152  				continue
   153  			}
   154  
   155  			// Server responds with results
   156  			// external bs buffer
   157  			responseMessage, err := conn.ReadMessageWithTrackerCallBack(pair.BsBuffer, pair.ResponseCallback)
   158  			if err != nil {
   159  				if conn.metrics != nil {
   160  					conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   161  				}
   162  
   163  				lastErr = xerrors.Errorf("failed to receive a response message: %w", err)
   164  				pair.Error = lastErr
   165  				outputPair <- pair
   166  				continue
   167  			}
   168  
   169  			err = conn.getResponse(responseMessage, pair.Response, true)
   170  			if err != nil {
   171  				if conn.metrics != nil {
   172  					conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   173  				}
   174  
   175  				lastErr = xerrors.Errorf("failed to parse response message: %w", err)
   176  				pair.Error = lastErr
   177  				outputPair <- pair
   178  				continue
   179  			}
   180  
   181  			outputPair <- pair
   182  		}
   183  	}()
   184  
   185  	return outputPair
   186  }
   187  
   188  // RequestForPassword sends a request and expects a response. XML escape only for '&'
   189  // bsBuffer is optional
   190  func (conn *IRODSConnection) RequestForPassword(request Request, response Response, bsBuffer []byte) error {
   191  	requestMessage, err := conn.getRequestMessage(request, true, true)
   192  	if err != nil {
   193  		if conn.metrics != nil {
   194  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   195  		}
   196  		return err
   197  	}
   198  
   199  	err = conn.SendMessage(requestMessage)
   200  	if err != nil {
   201  		if conn.metrics != nil {
   202  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   203  		}
   204  		return xerrors.Errorf("failed to send a request message: %w", err)
   205  	}
   206  
   207  	// Server responds with results
   208  	// external bs buffer
   209  	responseMessage, err := conn.ReadMessage(bsBuffer)
   210  	if err != nil {
   211  		if conn.metrics != nil {
   212  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   213  		}
   214  		return xerrors.Errorf("failed to receive a response message: %w", err)
   215  	}
   216  
   217  	err = conn.getResponse(responseMessage, response, true)
   218  	if err != nil {
   219  		if conn.metrics != nil {
   220  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   221  		}
   222  		return xerrors.Errorf("failed to parse response message: %w", err)
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // RequestWithoutResponse sends a request but does not wait for a response.
   229  func (conn *IRODSConnection) RequestWithoutResponse(request Request) error {
   230  	requestMessage, err := conn.getRequestMessage(request, true, false)
   231  	if err != nil {
   232  		if conn.metrics != nil {
   233  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   234  		}
   235  		return err
   236  	}
   237  
   238  	err = conn.SendMessage(requestMessage)
   239  	if err != nil {
   240  		if conn.metrics != nil {
   241  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   242  		}
   243  		return xerrors.Errorf("failed to send a request message: %w", err)
   244  	}
   245  
   246  	return nil
   247  }
   248  
   249  // RequestWithoutResponseNoXML sends a request but does not wait for a response.
   250  func (conn *IRODSConnection) RequestWithoutResponseNoXML(request Request) error {
   251  	requestMessage, err := conn.getRequestMessage(request, false, false)
   252  	if err != nil {
   253  		if conn.metrics != nil {
   254  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   255  		}
   256  		return xerrors.Errorf("failed to make a request message: %w", err)
   257  	}
   258  
   259  	err = conn.SendMessage(requestMessage)
   260  	if err != nil {
   261  		if conn.metrics != nil {
   262  			conn.metrics.IncreaseCounterForRequestResponseFailures(1)
   263  		}
   264  		return xerrors.Errorf("failed to send a request message: %w", err)
   265  	}
   266  
   267  	return nil
   268  }
   269  
   270  // RequestAndCheck sends a request and expects a CheckErrorResponse, on which the error is already checked.
   271  func (conn *IRODSConnection) RequestAndCheck(request Request, response CheckErrorResponse, bsBuffer []byte) error {
   272  	return conn.RequestAndCheckWithTrackerCallBack(request, response, bsBuffer, nil, nil)
   273  }
   274  
   275  // RequestAndCheckWithCallBack sends a request and expects a CheckErrorResponse, on which the error is already checked.
   276  func (conn *IRODSConnection) RequestAndCheckWithTrackerCallBack(request Request, response CheckErrorResponse, bsBuffer []byte, reqCallback common.TrackerCallBack, resCallback common.TrackerCallBack) error {
   277  	if err := conn.RequestWithTrackerCallBack(request, response, bsBuffer, reqCallback, resCallback); err != nil {
   278  		return err
   279  	}
   280  
   281  	return response.CheckError()
   282  }
   283  
   284  // RequestAndCheckForPassword sends a request and expects a CheckErrorResponse, on which the error is already checked.
   285  // Only escape '&'
   286  func (conn *IRODSConnection) RequestAndCheckForPassword(request Request, response CheckErrorResponse, bsBuffer []byte) error {
   287  	if err := conn.RequestForPassword(request, response, bsBuffer); err != nil {
   288  		return err
   289  	}
   290  
   291  	return response.CheckError()
   292  }
   293  
   294  func (conn *IRODSConnection) getRequestMessage(request Request, xml bool, forPassword bool) (*message.IRODSMessage, error) {
   295  	requestMessage, err := request.GetMessage()
   296  	if err != nil {
   297  		return nil, xerrors.Errorf("failed to make a request message: %w", err)
   298  	}
   299  
   300  	if xml {
   301  		// translate xml.Marshal XML into irods-understandable XML (among others, replace &#34; by &quot;)
   302  		err = conn.PreprocessMessage(requestMessage, forPassword)
   303  		if err != nil {
   304  			return nil, xerrors.Errorf("failed to send preprocess message: %w", err)
   305  		}
   306  	}
   307  
   308  	return requestMessage, nil
   309  }
   310  
   311  func (conn *IRODSConnection) getResponse(responseMessage *message.IRODSMessage, response Response, xml bool) error {
   312  	if xml {
   313  		// translate irods-dialect XML into valid XML
   314  		err := conn.PostprocessMessage(responseMessage)
   315  		if err != nil {
   316  			return xerrors.Errorf("failed to send postprocess message: %w", err)
   317  		}
   318  	}
   319  
   320  	err := response.FromMessage(responseMessage)
   321  	if err != nil {
   322  		return xerrors.Errorf("failed to parse a response message: %w", err)
   323  	}
   324  
   325  	return nil
   326  }