AckerApple / angular-file

Angular components for user file select, drop, and more
http://ackerapple.github.io/angular-file/
MIT License
128 stars 40 forks source link

Service Example to receive files in FormData #18

Closed alignsoft closed 6 years ago

alignsoft commented 6 years ago

As using FormData to send files to a server is relatively new to Angular2 and the new HttpClient, I'm finding it difficult to know how to best receive files on the server. Not being able to view the file data payload on the client side (other than interrogating the network payload itself) is making it difficult to write server side code.

An examples of server code implementing a simple receiver would make adopting this approach much more accessible.

AckerApple commented 6 years ago

Wellllllllllllll, we all use Angular if we use this package BUTTTTTTT we all seem to be using different servers

I would just suggest Googling "Server-side FormData processing examples"

AckerApple commented 6 years ago

I mean, FormData is a standardized W3C HTML5 item. We should master working with it in general, regardless of this package and Angular.

I'm like an eyelash twitch away from closing this issue based on how I see this. Since I very first forked this code, my intension was to take this package from old school crap to updated techniques. You wanna stay stuck old school, go with the original package.

figuerres commented 6 years ago

on windows server with iis i use webapi 2.0 and the endpoint is an http post that uses a standard component var streamProvider = new MultipartFormDataStreamProvider(ServerUploadFolder); await Request.Content.ReadAsMultipartAsync(streamProvider);

and then foreach (MultipartFileData item in streamProvider.FileData) {

i have been meaning to put this code online but other things keep me busy....

alignsoft commented 6 years ago

The problem, for me, is that I'm responsible for front end development only (which is why we're using Angular), and I'm trying to keep guiding the entire team who are writing all the server side code and the entire stack to current technology, and there's always resistance, always that 'we know the old rusty, which should we use the new shiny'. I'm sure I'm not alone in my working environment.

Having anything to point to that says 'Here's something you can see and extrapolate from' is incredibly useful when dealing with a 'show don't tell' team. They are smart guys, and can figure things out if they can see an example of what they need to learn.

It's easy to say 'Google FooBar' when you have a working knowledge of what FooBar is composed of, and if Googling 'server side code for handling/receiving/extracting files from FormData POST data' had gotten me anything useful, I wouldn't be asking.

You mentioned earlier that you have (I think) a node.js server and Java code on two different application instances, and with @figuerres webapi 2.0 example that's three bits of reference code that would be very useful to someone without the server side experience you have.

Again - reducing the amount of friction required to adopt something like this goes a long way to driving adoption, and because this is new, there's not a lot of documentation with context that's easy to find. This clearly isn't about me wanting to stay 'old school', I've already made the jump, it's me needing a little guidance in breaking the inertia for the guys on the server side of my fence.

AckerApple commented 6 years ago

I'm going to try to make this as simple as possible for you:

<form method="post" enctype="multipart/form-data"></form> IS 100% the same protocol as the FormData HTML5 class.

Have you ever, ever, ever, ever, in your career, sent files using enctype="multipart/form-data" ??? It's the saaaaaaaaaaaaaammmmmmmmmme exact protocol used when transmitting files using Javascript's FormData... It's multipart/form-data but instead of the <form> tag transmitting the data, it's Javascript.

I feel it's spelled out in a basic education document, as seen here: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript

(also, again, I like being dramatic and I'm not heated)

alignsoft commented 6 years ago

The problem isn't the front end - I have implemented angular-file, the browser works great, I can select files, I'm using FormData.append to add key/value pairs to send other required data back with the files so that the files can be associated with the correct items in the database, and those key/value pairs are extractable on the server side without any difficulty.

The filedata itself, while visible as a separate item in the network tab of the browser as base64encoded data doesn't seem to be extractable on the server side, and I don't know if that's because there's a problem sending it (however it's in the network payload as far as I can see), so I'm assuming that the problem is the guy's writing the server side code don't know how to extract the file data.

On the network tab in Chrome, I see this: data:image/jpeg/base64

Request URL:data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/...

And below it: api/

------WebKitFormBoundaryKfchX9MJnOAjMAn3
Content-Disposition: form-data; name:"file"; filename="Thumb2.jpg"
Content-Type: image/jpeg

------WebKitFormBoundaryKfchX9MJnOAjMAn3
Content-Disposition: form-data; name="groupID"

9003271
------WebKitFormBoundaryKfchX9MJnOAjMAn3
Content-Disposition: form-data; name="userID"

demouser
------WebKitFormBoundaryKfchX9MJnOAjMAn3--

The image data is complete - I can copy/paste that into a service that writes out a valid file, but I'm being told the file data isn't actually in the FormData I'm submitting, and I have nothing to point to for them to try.

It's entirely possible that we are doing everything exactly correctly, and there's a bug in my client side implementation, but I can't know that for certain either way at this point. I think I've implemented things correctly, and the server guys think they have implemented things correctly, and as the guy pushing the new shiny, the burden is on me to prove one way or the other.

AckerApple commented 6 years ago

Try to see if your server can process this than:

<form action="http://..." method="post" enctype="multipart/form-data">
  <input type="file" name="file" />
</form>

Based on the article I provided, and my recent experience: Processing the form tag multipart/form-data, on the server side, is exactly like the FormData class. That is to say, on the server, you should see no difference.

The article I linked, is what you should be sharing with the people you are trying to "convince". At the bottom of the article is states:

Conclusion

Depending on the browser, sending form data through JavaScript can be easy or difficult. The FormData object is generally the answer, and don't hesitate to use a polyfill for it on legacy browsers

figuerres commented 6 years ago

@alignsoft what is the back end server platform ? if it is IIS i can get my code and put it on line for your back end folks to check out.

if not IIS what are they using ?

you can also reach me as denny at figuerres dot com

hope that can help.

alignsoft commented 6 years ago

@figuerres Thanks Denny - it's Java on the backend. Cheers!

AckerApple commented 6 years ago

Here is the Java code we are using, I can't take the time to explain NOR strip out useless parts. But maybe it will help you along. Good luck

import java.io.IOException;
import java.io.InputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Types;
import java.util.ArrayList;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.aviorsciences.MysqlConnect;
import com.aviorsciences.HttpUtil.CustomResponses;
import com.aviorsciences.HttpUtil.exceptions.BadRequest;
import com.aviorsciences.HttpUtil.exceptions.Forbidden;
import com.aviorsciences.HttpUtil.exceptions.NotFound;
import com.aviorsciences.HttpUtil.firebase.Firebase;
import com.aviorsciences.HttpUtil.pojo.JsonBase;
import com.caringondemand.TapForCare._constants.ProcConstants;
import com.caringondemand.TapForCare._constants.S3Constants;
import com.caringondemand.TapForCare._constants.UrlConstants;
import com.caringondemand.TapForCare._enums.Permissions;
import com.caringondemand.TapForCare.endpoints.helpers.DashboardEndpointHelpers;
import com.caringondemand.TapForCare.endpoints.helpers.FacilityEndpointHelpers;
import com.caringondemand.TapForCare.endpoints.helpers.OrganizationalUnitEndpointHelpers;
import com.caringondemand.TapForCare.endpoints.helpers.UserEndpointHelpers;
import com.caringondemand.TapForCare.endpoints.pojo.Agency;
import com.caringondemand.TapForCare.endpoints.pojo.CaregiversByStatus;
import com.caringondemand.TapForCare.endpoints.pojo.DashboardInfo;
import com.caringondemand.TapForCare.endpoints.pojo.DashboardInfo2;
import com.caringondemand.TapForCare.endpoints.pojo.Facility;
import com.caringondemand.TapForCare.endpoints.pojo.FacilityArea;
import com.caringondemand.TapForCare.endpoints.pojo.MUserReq;
import com.caringondemand.TapForCare.endpoints.pojo.Roc;
import com.caringondemand.TapForCare.endpoints.pojo.RocVisitInfo;
import com.caringondemand.TapForCare.endpoints.pojo.RocsByStatus;
import com.caringondemand.TapForCare.endpoints.pojo.UserAccessIds;
import com.caringondemand.TapForCare.endpoints.pojo.UserFacility;
import com.caringondemand.TapForCare.endpoints.pojo.Wifi;
import com.caringondemand.TapForCare.endpoints.pojo.firebase.VisitInOut;
import com.caringondemand.TapForCare.utils.ArrayListUtil;
import com.caringondemand.TapForCare.utils.AwsUtil;
import com.caringondemand.TapForCare.utils.BadRequestUtil;
import com.caringondemand.TapForCare.utils.PermissionUtil;
import com.sun.jersey.api.core.ResourceContext;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;

@Path("/facility")
public class FacilityV1Endpoints {
  @Context ResourceContext resCtx;

  @Context
  HttpServletRequest req;

  @POST
  @Path("/file")
  @Consumes(MediaType.MULTIPART_FORM_DATA)
  @Produces(MediaType.APPLICATION_JSON)
  public Response uploadFile (
      @FormDataParam("file") InputStream fileInputStream,
      @FormDataParam("file") FormDataContentDisposition fileContent,
      @FormDataParam("path") String path,
      @FormDataParam("id") Integer id) throws Exception {

    MUserReq ruser = UserEndpointHelpers.getRequestUser(req);
    if (!ruser.isGod()) {
      if (!ruser.isFac())
        PermissionUtil.ForbiddenRoleCreate(ruser.getRole(), Permissions.FILE);
    }

    //TODO:remove these lines when the below todo is done.
    String fileName = fileContent.getFileName();
    String key = fileName;
    if (path.length() > 0)
      key = path + "/" + fileName;

    //TODO:remove this outer if statement once implemented, and create the path yourself.
    if (id != null) {
      try(MysqlConnect conn = new MysqlConnect(UrlConstants.FACILITY_DB);) {
        UserAccessIds ids = UserEndpointHelpers.getAccessIds(conn, ruser);
        if (!ruser.isGod()) ids.checkAccessForFacilityID(id);
      }

      path = UrlConstants.ENV.getS3BucketPath() + "/" + fileContent.getFileName();
    }

    AwsUtil aws = new AwsUtil();
    aws.S3_UploadSingleFile(S3Constants.facilityBucket, key, fileInputStream);
    return CustomResponses.succesfulResponse();

  }
}